1use crate::testing::assertions::{AssertionContract, AssertionLabel, AssertionValue};
6use crate::testing::specs::{SpecViolation, TBSpec};
7use crate::trace::{ConsumedTrace, ExecutionMode};
8
9#[cfg(not(feature = "std"))]
10use alloc::{borrow::Cow, string::String, vec::Vec};
11#[cfg(not(feature = "std"))]
12use core::sync::atomic::{AtomicBool, Ordering};
13#[cfg(feature = "std")]
14use std::borrow::Cow;
15
16#[cfg(feature = "digest")]
17use crate::crypto::hash::{Digest, Sha3_256};
18#[cfg(feature = "policy")]
19use crate::policy::TransitStatus;
20#[cfg(feature = "testing-timing")]
21use crate::testing::schedulability::{SchedulerType, TaskSet};
22#[cfg(feature = "derive")]
23use crate::Errorizable;
24
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub struct Cardinality {
31 min: u32,
32 max: Option<u32>,
33 must_be_present: bool,
34}
35
36impl Cardinality {
37 pub const fn new(min: u32, max: Option<u32>, must_be_present: bool) -> Self {
38 Self { min, max, must_be_present }
39 }
40 pub const fn exactly(n: u32) -> Self {
41 Self { min: n, max: Some(n), must_be_present: n > 0 }
42 }
43 pub const fn at_least(n: u32) -> Self {
44 Self { min: n, max: None, must_be_present: n > 0 }
45 }
46 pub const fn at_most(n: u32) -> Self {
47 Self { min: 0, max: Some(n), must_be_present: false }
48 }
49 pub const fn between(min: u32, max: u32) -> Self {
50 Self { min, max: Some(max), must_be_present: min > 0 }
51 }
52 pub const fn present() -> Self {
53 Self { min: 1, max: None, must_be_present: true }
54 }
55 pub const fn absent() -> Self {
56 Self { min: 0, max: Some(0), must_be_present: false }
57 }
58 pub fn describe(&self) -> String {
59 match (self.min, self.max) {
60 (0, Some(0)) => "absent".into(),
61 (m, Some(n)) if m == n => format!("exactly {m}"),
62 (m, Some(n)) => format!("between {m} and {n}"),
63 (0, None) => "any".into(),
64 (m, None) => format!("at least {m}"),
65 }
66 }
67 pub fn is_satisfied_by(&self, count: usize) -> bool {
68 let c = count as u32;
69 if c < self.min {
70 return false;
71 }
72 if let Some(mx) = self.max {
73 if c > mx {
74 return false;
75 }
76 }
77 true
78 }
79 pub fn min(&self) -> u32 {
80 self.min
81 }
82 pub fn max(&self) -> Option<u32> {
83 self.max
84 }
85 pub fn must_be_present(&self) -> bool {
86 self.must_be_present
87 }
88}
89
90pub const fn between(min: u32, max: u32) -> Cardinality {
91 Cardinality::between(min, max)
92}
93pub const fn present() -> Cardinality {
94 Cardinality::present()
95}
96pub const fn absent() -> Cardinality {
97 Cardinality::absent()
98}
99
100#[derive(Debug)]
106#[cfg_attr(feature = "derive", derive(Errorizable))]
107pub enum SpecBuildError {
108 #[cfg_attr(feature = "derive", error("Duplicate label: {0}"))]
109 DuplicateLabel(&'static str),
110 #[cfg_attr(feature = "derive", error("Unknown ordering label: {0}"))]
111 UnknownOrderingLabel(&'static str),
112 #[cfg_attr(feature = "derive", error("Invalid range: {0}"))]
113 InvalidRange(&'static str),
114}
115
116#[derive(Debug, Clone)]
118pub struct AssertSpecBuilder {
119 id: &'static str,
120 execution_mode: ExecutionMode,
121 gate_decision: Option<TransitStatus>,
122 version_major: u16,
123 version_minor: u16,
124 version_patch: u16,
125 assertions: Vec<(&'static str, Vec<&'static str>, Cardinality, Option<AssertionValue>)>,
126 tag_filter: Option<Vec<&'static str>>,
127 ordering: Vec<&'static str>,
128 #[cfg(feature = "instrument")]
129 required_events: Vec<crate::utils::urn::Urn<'static>>,
130 description: Option<&'static str>,
131 #[cfg(feature = "testing-timing")]
132 schedulability: Option<SchedulabilityAssertion>,
133}
134
135impl AssertSpecBuilder {
136 pub fn new(id: &'static str, execution_mode: ExecutionMode) -> Self {
137 Self {
138 id,
139 execution_mode,
140 gate_decision: None,
141 version_major: 1,
142 version_minor: 0,
143 version_patch: 0,
144 assertions: Vec::new(),
145 tag_filter: None,
146 ordering: Vec::new(),
147 #[cfg(feature = "instrument")]
148 required_events: Vec::new(),
149 description: None,
150 #[cfg(feature = "testing-timing")]
151 schedulability: None,
152 }
153 }
154
155 pub fn version(mut self, maj: u16, min: u16, patch: u16) -> Self {
156 self.version_major = maj;
157 self.version_minor = min;
158 self.version_patch = patch;
159 self
160 }
161
162 pub fn gate_decision(mut self, decision: TransitStatus) -> Self {
163 self.gate_decision = Some(decision);
164 self
165 }
166
167 pub fn tag_filter(mut self, tags: Vec<&'static str>) -> Self {
168 self.tag_filter = Some(tags);
169 self
170 }
171
172 pub fn assertion(
173 mut self,
174 label: &'static str,
175 tags: Vec<&'static str>,
176 cardinality: Cardinality,
177 ) -> Result<Self, SpecBuildError> {
178 if self.assertions.iter().any(|(l, _, _, _)| *l == label) {
179 return Err(SpecBuildError::DuplicateLabel(label));
180 }
181 if let Some(mx) = cardinality.max {
182 if mx < cardinality.min {
183 return Err(SpecBuildError::InvalidRange(label));
184 }
185 }
186 self.assertions.push((label, tags, cardinality, None));
187 Ok(self)
188 }
189
190 pub fn assertion_with_value(
191 mut self,
192 label: &'static str,
193 tags: Vec<&'static str>,
194 cardinality: Cardinality,
195 expected_value: Option<AssertionValue>,
196 ) -> Result<Self, SpecBuildError> {
197 if self.assertions.iter().any(|(l, _, _, _)| *l == label) {
198 return Err(SpecBuildError::DuplicateLabel(label));
199 }
200 if let Some(mx) = cardinality.max {
201 if mx < cardinality.min {
202 return Err(SpecBuildError::InvalidRange(label));
203 }
204 }
205 self.assertions.push((label, tags, cardinality, expected_value));
206 Ok(self)
207 }
208
209 pub fn ordering(mut self, labels: &[&'static str]) -> Result<Self, SpecBuildError> {
210 for &lbl in labels {
211 if !self.assertions.iter().any(|(l, _, _, _)| *l == lbl) {
212 return Err(SpecBuildError::UnknownOrderingLabel(lbl));
213 }
214 self.ordering.push(lbl);
215 }
216 Ok(self)
217 }
218
219 #[cfg(feature = "instrument")]
220 pub fn required_events(mut self, kinds: &[crate::utils::urn::Urn<'static>]) -> Self {
221 use std::collections::HashSet;
222 let mut seen = HashSet::new();
223 for k in kinds {
224 if seen.insert(k.clone()) {
225 self.required_events.push(k.clone());
226 }
227 }
228 self
229 }
230
231 pub fn description(mut self, desc: &'static str) -> Self {
232 self.description = Some(desc);
233 self
234 }
235
236 #[cfg(feature = "testing-timing")]
237 pub fn schedulability(mut self, assertion: SchedulabilityAssertion) -> Self {
238 self.schedulability = Some(assertion);
239 self
240 }
241
242 pub fn build(self) -> BuiltAssertSpec {
243 BuiltAssertSpec::from_builder(self)
244 }
245}
246
247#[cfg(feature = "testing-timing")]
249#[derive(Debug, Clone)]
250pub struct SchedulabilityAssertion {
251 pub task_set: TaskSet,
253 pub must_be_schedulable: bool,
255}
256
257#[derive(Debug, Clone)]
258pub struct BuiltAssertSpec {
259 inner: AssertSpecBuilder,
260 contracts: Box<[AssertionContract]>,
261 spec_hash: [u8; 32],
262}
263
264impl BuiltAssertSpec {
265 fn from_builder(builder: AssertSpecBuilder) -> Self {
266 let tag_filter = builder.tag_filter.clone();
267 let contracts: Vec<AssertionContract> = builder
268 .assertions
269 .iter()
270 .map(|(label, _tags, card, value)| {
271 let mut contract = if let Some(ref val) = value {
272 AssertionContract::new(AssertionLabel::Custom(Cow::Borrowed(label)), *card).with_value(val.clone())
273 } else {
274 AssertionContract::new(AssertionLabel::Custom(Cow::Borrowed(label)), *card)
275 };
276 if let Some(ref filter) = tag_filter {
277 contract = contract.with_tag_filter(filter.clone());
278 }
279 contract
280 })
281 .collect();
282 let spec_hash = Self::compute_hash(
283 builder.id,
284 builder.execution_mode,
285 builder.gate_decision,
286 builder.version_major,
287 builder.version_minor,
288 builder.version_patch,
289 &contracts,
290 builder.tag_filter.as_deref(),
291 #[cfg(feature = "instrument")]
292 &builder.required_events,
293 #[cfg(feature = "testing-timing")]
294 builder.schedulability.as_ref(),
295 );
296 Self { inner: builder, contracts: contracts.into_boxed_slice(), spec_hash }
297 }
298
299 #[allow(clippy::too_many_arguments)]
300 fn compute_hash(
301 id: &'static str,
302 mode: ExecutionMode,
303 gate: Option<TransitStatus>,
304 version_major: u16,
305 version_minor: u16,
306 version_patch: u16,
307 contracts: &[AssertionContract],
308 tag_filter: Option<&[&'static str]>,
309 #[cfg(feature = "instrument")] events: &[crate::utils::urn::Urn<'static>],
310 #[cfg(feature = "testing-timing")] schedulability: Option<&SchedulabilityAssertion>,
311 ) -> [u8; 32] {
312 let mut h = Sha3_256::new();
313 h.update(b"TBSP");
315 h.update(version_major.to_be_bytes());
316 h.update(version_minor.to_be_bytes());
317 h.update(version_patch.to_be_bytes());
318 h.update(id.as_bytes());
319 let mode_code = match mode {
320 ExecutionMode::Accept => 0u8,
321 ExecutionMode::Reject => 1u8,
322 ExecutionMode::Error => 2u8,
323 };
324 h.update([mode_code]);
325 match gate {
326 Some(g) => {
327 h.update([1u8]);
328 h.update([g as u8]);
329 }
330 None => h.update([0u8]),
331 }
332 if let Some(tags) = tag_filter {
334 h.update([1u8]);
335 h.update((tags.len() as u32).to_be_bytes());
336 for tag in tags {
337 h.update(tag.as_bytes());
338 }
339 } else {
340 h.update([0u8]);
341 }
342 let mut norm: Vec<(&str, u32, Option<u32>, bool)> = Vec::with_capacity(contracts.len());
344 for c in contracts {
345 let AssertionLabel::Custom(lbl) = &c.label;
346 norm.push((
347 lbl.as_ref(),
348 c.cardinality.min,
349 c.cardinality.max,
350 c.cardinality.must_be_present,
351 ));
352 }
353 norm.sort_by(|a, b| a.0.cmp(b.0)); for (lbl, min, max, must) in norm {
355 h.update(lbl.as_bytes());
356 h.update(min.to_be_bytes());
357 match max {
358 Some(m) => {
359 h.update([1u8]);
360 h.update(m.to_be_bytes());
361 }
362 None => h.update([0u8]),
363 }
364 h.update([must as u8]);
365 }
366 #[cfg(feature = "instrument")]
367 {
368 let mut sorted_events: Vec<String> = events.iter().map(|e| e.to_string()).collect();
370 sorted_events.sort();
371 for urn_str in sorted_events {
372 h.update(urn_str.as_bytes());
373 }
374 }
375 #[cfg(feature = "testing-timing")]
376 {
377 if let Some(schedule) = schedulability {
378 h.update([1u8]); h.update([schedule.task_set.scheduler as u8]);
380 h.update([schedule.must_be_schedulable as u8]);
381 h.update((schedule.task_set.tasks.len() as u32).to_be_bytes());
382 let mut tasks: Vec<_> = schedule.task_set.tasks.iter().collect();
384 tasks.sort_by(|a, b| a.id.cmp(&b.id));
385 for task in tasks {
386 h.update(task.id.as_bytes());
387 h.update(task.period.as_nanos().to_be_bytes());
388 h.update(task.deadline.as_nanos().to_be_bytes());
389 h.update(task.wcet.as_nanos().to_be_bytes());
390 }
391 } else {
392 h.update([0u8]); }
394 }
395 let out = h.finalize();
396 let mut arr = [0u8; 32];
397 arr.copy_from_slice(&out);
398 arr
399 }
400
401 pub fn spec_hash(&self) -> [u8; 32] {
402 self.spec_hash
403 }
404 pub fn version(&self) -> (u16, u16, u16) {
405 (self.inner.version_major, self.inner.version_minor, self.inner.version_patch)
406 }
407}
408
409impl TBSpec for BuiltAssertSpec {
410 fn id(&self) -> &'static str {
411 self.inner.id
412 }
413 fn mode(&self) -> ExecutionMode {
414 self.inner.execution_mode
415 }
416 fn required_assertions(&self) -> &[AssertionContract] {
417 &self.contracts
418 }
419 fn expected_gate_decision(&self) -> Option<TransitStatus> {
420 self.inner.gate_decision
421 }
422 #[cfg(feature = "instrument")]
423 fn required_events(&self) -> &[crate::utils::urn::Urn<'static>] {
424 &self.inner.required_events
425 }
426 fn validate_trace(&self, _trace: &ConsumedTrace) -> Result<(), SpecViolation> {
427 #[cfg(feature = "testing-timing")]
428 {
429 if let Some(ref schedule) = self.inner.schedulability {
430 return Self::check_schedulability(schedule);
431 }
432 }
433 Ok(())
434 }
435}
436
437#[cfg(feature = "testing-timing")]
438impl BuiltAssertSpec {
439 fn check_schedulability(assertion: &SchedulabilityAssertion) -> Result<(), SpecViolation> {
441 use crate::testing::schedulability::{is_edf_schedulable, is_rm_schedulable};
442
443 let result = match assertion.task_set.scheduler {
444 SchedulerType::RateMonotonic => is_rm_schedulable(&assertion.task_set),
445 SchedulerType::EarliestDeadlineFirst => is_edf_schedulable(&assertion.task_set),
446 };
447
448 match result {
449 Ok(schedule_result) => {
450 let is_schedulable = schedule_result.is_schedulable;
451 if assertion.must_be_schedulable && !is_schedulable {
452 Err(SpecViolation::SchedulabilityViolation(schedule_result))
454 } else if !assertion.must_be_schedulable && is_schedulable {
455 let violation = crate::testing::schedulability::TaskViolationDetail {
458 task_id: "system".to_string(),
459 message: format!(
460 "Task set is schedulable (utilization: {:.3}, bound: {:.3}) but should not be",
461 schedule_result.utilization, schedule_result.utilization_bound
462 ),
463 };
464 let mut modified_result = schedule_result;
465 modified_result.violations = vec![violation];
466 Err(SpecViolation::SchedulabilityViolation(modified_result))
467 } else {
468 Ok(())
469 }
470 }
471 Err(e) => Err(SpecViolation::SchedulabilityError(e)),
472 }
473 }
474}
475
476pub trait AssertEncode {
482 fn tb_payload_bytes(&self) -> Cow<'_, [u8]>;
483}
484impl AssertEncode for u8 {
486 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
487 Cow::Owned(vec![*self])
488 }
489}
490impl AssertEncode for u16 {
491 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
492 Cow::Owned(self.to_be_bytes().to_vec())
493 }
494}
495impl AssertEncode for u32 {
496 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
497 Cow::Owned(self.to_be_bytes().to_vec())
498 }
499}
500impl AssertEncode for u64 {
501 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
502 Cow::Owned(self.to_be_bytes().to_vec())
503 }
504}
505impl AssertEncode for i8 {
507 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
508 Cow::Owned(vec![*self as u8])
509 }
510}
511impl AssertEncode for i16 {
512 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
513 Cow::Owned((*self as u16).to_be_bytes().to_vec())
514 }
515}
516impl AssertEncode for i32 {
517 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
518 Cow::Owned((*self as u32).to_be_bytes().to_vec())
519 }
520}
521impl AssertEncode for i64 {
522 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
523 Cow::Owned((*self as u64).to_be_bytes().to_vec())
524 }
525}
526impl AssertEncode for usize {
528 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
529 Cow::Owned((*self as u64).to_be_bytes().to_vec())
530 }
531}
532impl AssertEncode for isize {
533 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
534 Cow::Owned((*self as u64).to_be_bytes().to_vec())
535 }
536}
537impl AssertEncode for &str {
539 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
540 Cow::Borrowed(self.as_bytes())
541 }
542}
543impl AssertEncode for str {
544 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
545 Cow::Borrowed(self.as_bytes())
546 }
547}
548impl AssertEncode for &[u8] {
549 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
550 Cow::Borrowed(self)
551 }
552}
553impl AssertEncode for Vec<u8> {
554 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
555 Cow::Borrowed(self.as_slice())
556 }
557}
558impl AssertEncode for [u8; 32] {
559 fn tb_payload_bytes(&self) -> Cow<'_, [u8]> {
560 Cow::Borrowed(self)
561 }
562}
563
564pub fn __encode_payload<T: AssertEncode + ?Sized>(v: &T) -> Cow<'_, [u8]> {
565 T::tb_payload_bytes(v)
566}
567
568#[macro_export]
574macro_rules! exactly {
575 ($n:expr) => {
576 $crate::testing::macros::Cardinality::exactly($n)
577 };
578}
579#[macro_export]
580macro_rules! at_least {
581 ($n:expr) => {
582 $crate::testing::macros::Cardinality::at_least($n)
583 };
584}
585#[macro_export]
586macro_rules! at_most {
587 ($n:expr) => {
588 $crate::testing::macros::Cardinality::at_most($n)
589 };
590}
591#[macro_export]
592macro_rules! between {
593 ($min:expr, $max:expr) => {
594 $crate::testing::macros::Cardinality::between($min, $max)
595 };
596}
597#[macro_export]
598macro_rules! present {
599 () => {
600 $crate::testing::macros::Cardinality::present()
601 };
602}
603#[macro_export]
604macro_rules! absent {
605 () => {
606 $crate::testing::macros::Cardinality::absent()
607 };
608}
609
610#[doc(hidden)]
615#[macro_export]
616macro_rules! __tb_assert_spec_build_all {
617 (
618 $base:ident,
619 $desc_opt:expr,
620 $(
621 $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
622 assertions: [ $( $assertion:tt ),* ],
623 $(
624 events: [ $($events_tt:tt)* ],
625 )?
626 $(, tag_filter: [ $( $tag:expr ),* $(,)? ])?
627 $(, schedulability: { $($schedule_content:tt)* })?
628 )+
629 ) => {{
630 #[cfg(feature = "std")]
631 {
632 static CELL: std::sync::OnceLock<Vec<$crate::testing::macros::BuiltAssertSpec>> = std::sync::OnceLock::new();
633 CELL.get_or_init(|| {
634 let mut v = Vec::new();
635 $crate::__tb_assert_spec_build_all_impl!(
636 v, $base, $desc_opt,
637 $(
638 $maj, $min, $patch, $mode, $gate,
639 assertions: [ $( $assertion ),* ],
640 $(
641 events: [ $($events_tt)* ],
642 )?
643 $(, tag_filter: [ $( $tag ),* ])?
644 $(, schedulability: { $($schedule_content)* })?
645 )+
646 );
647 v
648 }).as_slice()
649 }
650 #[cfg(not(feature = "std"))]
651 {
652 use core::sync::atomic::{AtomicBool, Ordering};
653 static INIT: AtomicBool = AtomicBool::new(false);
654 static mut VEC: Option<Vec<$crate::testing::macros::BuiltAssertSpec>> = None;
655 if !INIT.load(Ordering::Acquire) {
656 let desc_opt = $desc_opt;
657 let mut v = Vec::new();
658 $crate::__tb_assert_spec_build_all_impl!(
659 v, $base, desc_opt,
660 $(
661 $maj, $min, $patch, $mode, $gate,
662 assertions: [ $( $assertion ),* ],
663 $(
664 events: [ $($events_tt)* ],
665 )?
666 $(, tag_filter: [ $( $tag ),* ])?
667 $(, schedulability: { $($schedule_content)* })?
668 )+
669 );
670 unsafe { VEC = Some(v); }
671 INIT.store(true, Ordering::Release);
672 }
673 unsafe { VEC.as_ref().unwrap().as_slice() }
674 }
675 }};
676}
677
678#[doc(hidden)]
680#[macro_export]
681macro_rules! __tb_assert_spec_build_all_impl {
682 (
683 $vec:ident, $base:ident, $desc_opt:expr,
684 $(
685 $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
686 assertions: [ $( $assertion:tt ),* ],
687 $(
688 events: [ $($events_tt:tt)* ],
689 )?
690 $(, tag_filter: [ $( $tag:expr ),* $(,)? ])?
691 $(, schedulability: { $($schedule_content:tt)* })?
692 )+
693 ) => {
694 $(
695 $crate::__tb_assert_spec_build_all_impl_with_events!(
696 $vec, $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
697 assertions: [ $( $assertion ),* ],
698 $(
699 events: [ $($events_tt:tt)* ],
700 )?
701 $(, tag_filter: [ $( $tag ),* ])?
702 $(, schedulability: { $($schedule_content)* })?
703 );
704 )+
705 };
706}
707
708#[doc(hidden)]
710#[macro_export]
711macro_rules! __tb_assert_spec_build_all_impl_with_events {
712 (
713 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
714 assertions: [ $( $assertion:tt ),* ],
715 $(
716 events: [ $($events_tt:tt)* ],
717 )?
718 $(, tag_filter: [ $( $tag:expr ),* ])?
719 $(, schedulability: { $($schedule_content:tt)* })?
720 ) => {{
721 $crate::__tb_assert_spec_build! {
724 @expand_events
725 $vec, $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
726 assertions: [ $( $assertion ),* ],
727 $(
728 events_tt: [ $($events_tt:tt)* ],
729 )?
730 $( tag_filter: [ $( $tag ),* ])?
731 $(, schedulability: { $($schedule_content)* })?
732 }
733 }};
734}
735
736#[doc(hidden)]
738#[macro_export]
739macro_rules! __tb_assert_spec_build {
740 (
741 $vec:ident,
742 $base:ident,
743 $maj:literal, $min:literal, $patch:literal,
744 $mode:ident, $gate:ident,
745 assertions: [ $( $assertion:tt ),* $(,)? ],
746 $(
747 events: [ $($events_tt:tt)* ],
748 )?
749 $(tag_filter: [ $( $tag:expr ),* $(,)? ])?
750 $(, description: $desc:expr)?
751 $(, schedulability: { $($schedule_content:tt)* })?
752 ) => {{
753 $crate::__tb_assert_spec_build! {
755 @expand_events
756 $vec, $base, $maj, $min, $patch, $mode, $gate,
757 assertions: [ $( $assertion ),* ],
758 $(
759 events_tt: [ $($events_tt:tt)* ],
760 )?
761 $( tag_filter: [ $( $tag ),* $(,)? ])?
762 $(, description: $desc)?
763 $(, schedulability: { $($schedule_content)* })?
764 }
765 }};
766 (@expand_events
770 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
771 assertions: [ $( $assertion:tt ),* ],
772 events_tt: [ $($events_tt:tt)* ],
773 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
774 $(, schedulability: { $($schedule_content:tt)* })?
775 ) => {
776 $crate::__tb_assert_spec_build! {
777 @build_with_events_expanded
778 $vec, $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
779 assertions: [ $( $assertion ),* ],
780 events_tt: [ $($events_tt:tt)* ],
781 $( tag_filter: [ $( $tag ),* $(,)? ])?
782 $(, schedulability: { $($schedule_content)* })?
783 }
784 };
785 (@expand_events
787 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
788 assertions: [ $( $assertion:tt ),* ],
789 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
790 $(, schedulability: { $($schedule_content:tt)* })?
791 ) => {
792 $crate::__tb_assert_spec_build! {
793 @build_with_events
794 $vec, $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
795 assertions: [ $( $assertion ),* ],
796 events: [ ],
797 $( tag_filter: [ $( $tag ),* ])?
798 $(, schedulability: { $($schedule_content)* })?
799 }
800 };
801 (@expand_events
803 $vec:ident, $base:ident, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
804 assertions: [ $( $assertion:tt ),* ],
805 $(
806 events_tt: [ $($events_tt:tt)* ],
807 )?
808 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
809 $(, description: $desc:expr)?
810 $(, schedulability: { $($schedule_content:tt)* })?
811 ) => {
812 $(
813 $crate::__tb_assert_spec_build! {
815 @build_with_events_expanded
816 $vec, $base, None, $maj, $min, $patch, $mode, $gate,
817 assertions: [ $( $assertion ),* ],
818 events_tt: [ $($events_tt:tt)* ],
819 $( tag_filter: [ $( $tag ),* $(,)? ])?
820 $(, description: $desc)?
821 $(, schedulability: { $($schedule_content)* })?
822 }
823 )?
824 $(
825 $crate::__tb_assert_spec_build! {
827 @build_with_events
828 $vec, $base, None, $maj, $min, $patch, $mode, $gate,
829 assertions: [ $( $assertion ),* ],
830 events: [ ],
831 $( tag_filter: [ $( $tag ),* $(,)? ])?
832 $(, description: $desc)?
833 $(, schedulability: { $($schedule_content)* })?
834 }
835 )?
836 };
837 (@build_with_events_expanded
838 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
839 assertions: [ $( $assertion:tt ),* ],
840 events_tt: [ $( $ev:ident ),* $(,)? ],
841 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
842 $(, description: $desc:expr)?
843 $(, schedulability: { $($schedule_content:tt)* })?
844 ) => {{
845 $crate::__tb_assert_spec_build! {
846 @build_with_events
847 $vec, $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
848 assertions: [ $( $assertion ),* ],
849 events: [ $( $ev ),* $(,)? ],
850 $( tag_filter: [ $( $tag ),* $(,)? ])?
851 $(, description: $desc)?
852 $(, schedulability: { $($schedule_content)* })?
853 }
854 }};
855 (@build_with_events_expanded
857 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
858 assertions: [ $( $assertion:tt ),* ],
859 events_tt: [ $($events_tt:tt)* ],
860 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
861 $(, description: $desc:expr)?
862 $(, schedulability: { $($schedule_content:tt)* })?
863 ) => {{
864 $crate::__tb_assert_spec_build! {
866 @build_with_events_expanded
867 $vec, $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
868 assertions: [ $( $assertion ),* ],
869 events_tt: [ $($events_tt)* ],
870 $( tag_filter: [ $( $tag ),* $(,)? ])?
871 $(, description: $desc)?
872 $(, schedulability: { $($schedule_content)* })?
873 }
874 }};
875 (@build_with_events_expanded
877 $vec:ident, $base:ident, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
878 assertions: [ $( $assertion:tt ),* ],
879 events_tt: [ $($events_tt:tt)* ],
880 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
881 $(, description: $desc:expr)?
882 $(, schedulability: { $($schedule_content:tt)* })?
883 ) => {{
884 $crate::__tb_assert_spec_build! {
886 @build_with_events_expanded
887 $vec, $base, None, $maj, $min, $patch, $mode, $gate,
888 assertions: [ $( $assertion ),* ],
889 events_tt: [ $($events_tt)* ],
890 $( tag_filter: [ $( $tag ),* $(,)? ])?
891 $(, description: $desc)?
892 $(, schedulability: { $($schedule_content)* })?
893 }
894 }};
895 (@build_with_events
896 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
897 assertions: [ $( $assertion:tt ),* ],
898 events: [ $( $ev:expr ),* $(,)? ],
899 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
900 $(, description: $desc:expr)?
901 $(, schedulability: { $($schedule_content:tt)* })?
902 ) => {{
903 let mut builder = $crate::__tb_assert_spec_init_builder!(
904 $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
905 $(tag_filter: [ $( $tag ),* ])?
906 $(, description: $desc)?
907 );
908 $(
909 builder = $crate::__tb_assert_spec_add_assertion!(builder, $assertion);
910 )*
911 #[cfg(feature = "instrument")]
912 {
913 $(
914 builder = builder.required_events(&[$ev.clone()]);
915 )*
916 }
917 $(
918 #[cfg(feature = "testing-timing")]
919 {
920 $crate::__tb_assert_spec_parse_schedulability!(builder, $($schedule_content)*);
921 }
922 )?
923 $vec.push(builder.build());
924 }};
925 (@expand_events
927 $vec:ident, $base:ident, $desc_opt:expr, $maj:literal, $min:literal, $patch:literal, $mode:ident, $gate:ident,
928 assertions: [ $( $assertion:tt ),* ],
929 events_tt: [ ],
930 $( tag_filter: [ $( $tag:expr ),* $(,)? ])?
931 $(, description: $desc:expr)?
932 $(, schedulability: { $($schedule_content:tt)* })?
933 ) => {{
934 let mut builder = $crate::__tb_assert_spec_init_builder!(
935 $base, $desc_opt, $maj, $min, $patch, $mode, $gate,
936 $(tag_filter: [ $( $tag ),* ])?
937 $(, description: $desc)?
938 );
939 $(
940 builder = $crate::__tb_assert_spec_add_assertion!(builder, $assertion);
941 )*
942 $(
943 #[cfg(feature = "testing-timing")]
944 {
945 $crate::__tb_assert_spec_parse_schedulability!(builder, $($schedule_content)*);
946 }
947 )?
948 $vec.push(builder.build());
949 }};
950}
951
952#[doc(hidden)]
954#[cfg(feature = "testing-timing")]
955#[macro_export]
956macro_rules! __tb_assert_spec_parse_schedulability {
957 (
958 $builder:ident,
959 task_set: $task_set:expr,
960 scheduler: $scheduler:ident,
961 must_be_schedulable: $must_be:expr,
962 ) => {{
963 use $crate::testing::schedulability::SchedulerType;
964 let task_set_with_scheduler = {
965 let mut ts = $task_set.clone();
966 ts.scheduler = SchedulerType::$scheduler;
967 ts
968 };
969 let assertion = $crate::testing::macros::SchedulabilityAssertion {
970 task_set: task_set_with_scheduler,
971 must_be_schedulable: $must_be,
972 };
973 $builder = $builder.schedulability(assertion);
974 }};
975}
976
977#[doc(hidden)]
979#[macro_export]
980macro_rules! __tb_assert_spec_add_assertion {
981 ($builder:expr, ($label:expr, $card:expr, equals!($value:expr), tags: [ $($tag:expr),* $(,)? ])) => {
983 $builder
984 .assertion_with_value($label, vec![ $($tag),* ], $card, Some($crate::testing::assertions::AssertionValue::from($value)))
985 .expect("duplicate label or invalid range")
986 };
987 ($builder:expr, ($label:expr, $card:expr, equals!($value:expr))) => {
989 $builder
990 .assertion_with_value($label, vec![], $card, Some($crate::testing::assertions::AssertionValue::from($value)))
991 .expect("duplicate label or invalid range")
992 };
993 ($builder:expr, ($label:expr, $card:expr, tags: [ $($tag:expr),* $(,)? ])) => {
995 $builder
996 .assertion($label, vec![ $($tag),* ], $card)
997 .expect("duplicate label or invalid range")
998 };
999 ($builder:expr, ($label:expr, $card:expr)) => {
1001 $builder
1002 .assertion($label, vec![], $card)
1003 .expect("duplicate label or invalid range")
1004 };
1005}
1006
1007#[macro_export]
1009macro_rules! tb_assert_spec {
1010 (
1011 $(#[$meta:meta])*
1012 $vis:vis $base:ident,
1013 $( V ( $maj:literal , $min:literal , $patch:literal ) : {
1014 mode: $mode:ident,
1015 gate: $gate:ident,
1016 $( tag_filter: [ $( $tag:expr ),* $(,)? ], )?
1017 assertions: [ $( $assertion:tt ),* $(,)? ]
1018 $(, events: [ $($events_tt:tt)* ])?
1019 $(, schedulability: { $($schedule_content:tt)* })?
1020 } ),+ $(,)?
1021 $(, annotations { description: $desc:expr })?
1022 ) => {
1023 $(#[$meta])*
1024 $vis struct $base;
1025 impl $base {
1026 pub fn all() -> &'static [$crate::testing::macros::BuiltAssertSpec] {
1027 let desc_opt: Option<&'static str> = None::<&'static str> $(.or(Some($desc)))?;
1028 $crate::__tb_assert_spec_build_all!(
1029 $base,
1030 desc_opt,
1031 $(
1032 $maj, $min, $patch, $mode, $gate,
1033 assertions: [ $( $assertion ),* ],
1034 $(
1035 events: [ $($events_tt:tt)* ],
1036 )?
1037 $(, tag_filter: [ $( $tag ),* ])?
1038 $(, schedulability: { $($schedule_content)* })?
1039 )+
1040 )
1041 }
1042
1043 #[allow(dead_code)]
1044 pub fn get(maj: u16, min: u16, patch: u16) -> Option<&'static $crate::testing::macros::BuiltAssertSpec> {
1045 for s in Self::all() {
1046 let (maj_ver, min_ver, patch_ver) = s.version();
1047 if maj_ver == maj && min_ver == min && patch_ver == patch {
1048 return Some(s);
1049 }
1050 }
1051 None
1052 }
1053
1054 #[allow(dead_code)]
1055 pub fn latest() -> &'static $crate::testing::macros::BuiltAssertSpec {
1056 let mut best: Option<&'static $crate::testing::macros::BuiltAssertSpec> = None;
1057 for s in Self::all() {
1058 match best {
1059 Some(b) => if s.version() > b.version() { best = Some(s); },
1060 None => best = Some(s)
1061 }
1062 }
1063 best.expect("no versions defined")
1064 }
1065 }
1066 };
1067}
1068
1069#[doc(hidden)]
1077#[macro_export]
1078macro_rules! __tb_scenario_validate_csp_fdr {
1079 (
1080 $trace:expr,
1081 $(csp: $csp:ty,)?
1082 $(fdr: $fdr_config:expr,)?
1083 ) => {{
1084 #[cfg(feature = "testing-csp")]
1086 let csp_result: Option<$crate::testing::specs::csp::CspValidationResult> = {
1087 $crate::tb_scenario!(@csp_validate $trace, $($csp)?)
1088 };
1089
1090 #[cfg(not(feature = "testing-csp"))]
1091 let csp_result: Option<$crate::testing::specs::csp::CspValidationResult> = None;
1092
1093 #[cfg(feature = "testing-csp")]
1095 let csp_failed = csp_result.as_ref().map(|r| !r.valid).unwrap_or(false);
1096 #[cfg(not(feature = "testing-csp"))]
1097 let csp_failed = false;
1098
1099 #[cfg(feature = "testing-fdr")]
1101 let (fdr_result, fdr_config): (Option<$crate::testing::fdr::FdrVerdict>, Option<$crate::testing::fdr::FdrConfig>) = {
1102 $crate::tb_scenario!(@fdr_validate_with_config $trace, $($fdr_config)?)
1103 };
1104
1105 #[cfg(not(feature = "testing-fdr"))]
1106 let (fdr_result, fdr_config): (Option<$crate::testing::fdr::FdrVerdict>, Option<$crate::testing::fdr::FdrConfig>) = (None, None);
1107
1108 #[cfg(feature = "testing-fdr")]
1110 let fdr_failed = fdr_result.as_ref().map(|v| !v.passed).unwrap_or(false);
1111 #[cfg(not(feature = "testing-fdr"))]
1112 let fdr_failed = false;
1113
1114 #[cfg(feature = "testing-fdr")]
1116 let expect_failure = fdr_config.as_ref().map(|c| c.expect_failure).unwrap_or(false);
1117 #[cfg(not(feature = "testing-fdr"))]
1118 let expect_failure = false;
1119
1120 (csp_result, csp_failed, fdr_result, fdr_config, fdr_failed, expect_failure)
1121 }};
1122}
1123
1124#[doc(hidden)]
1126#[macro_export]
1127macro_rules! __tb_scenario_call_hooks {
1128 (
1129 scenario_result: $scenario_result:expr,
1130 csp_failed: $csp_failed:expr,
1131 fdr_failed: $fdr_failed:expr,
1132 expect_failure: $expect_failure:expr,
1133 $(hooks: {
1134 $(on_pass: $on_pass:expr,)?
1135 $(on_fail: $on_fail:expr)?
1136 },)?
1137 ) => {{
1138 #[allow(unreachable_code)]
1139 #[allow(unused_labels)]
1140 let hook_result: Result<(), Box<dyn std::error::Error>> = 'hook_call: {
1141 if $scenario_result.passed {
1142 $(
1144 $(
1145 fn __call_hook<F>(f: F, trace: &$crate::trace::ConsumedTrace, result: &$crate::testing::ScenarioResult) -> Result<(), Box<dyn std::error::Error>>
1146 where
1147 F: FnOnce(&$crate::trace::ConsumedTrace, &$crate::testing::ScenarioResult) -> Result<(), Box<dyn std::error::Error>>,
1148 {
1149 f(trace, result)
1150 }
1151 break 'hook_call __call_hook($on_pass, &$scenario_result.trace, &$scenario_result);
1152 )?
1153 )?
1154 Ok(())
1155 } else {
1156 $(
1158 $(
1159 fn __call_hook<F>(f: F, trace: &$crate::trace::ConsumedTrace, result: &$crate::testing::ScenarioResult) -> Result<(), Box<dyn std::error::Error>>
1160 where
1161 F: FnOnce(&$crate::trace::ConsumedTrace, &$crate::testing::ScenarioResult) -> Result<(), Box<dyn std::error::Error>>,
1162 {
1163 f(trace, result)
1164 }
1165 break 'hook_call __call_hook($on_fail, &$scenario_result.trace, &$scenario_result);
1166 )?
1167 )?
1168 Err(format!("{}", $scenario_result).into())
1170 }
1171 };
1172 hook_result
1173 }};
1174}
1175
1176#[doc(hidden)]
1178#[macro_export]
1179macro_rules! __tb_assert_spec_init_builder {
1180 (
1181 $base:ident,
1182 $desc_opt:expr,
1183 $maj:literal,
1184 $min:literal,
1185 $patch:literal,
1186 $mode:ident,
1187 $gate:ident,
1188 $(tag_filter: [ $($tag:expr),* $(,)? ])?
1189 $(, description: $desc:expr)?
1190 ) => {{
1191 let (maj, min, patch) = ($maj as u16, $min as u16, $patch as u16);
1192 let mut builder = $crate::testing::macros::AssertSpecBuilder::new(
1193 stringify!($base),
1194 $crate::trace::ExecutionMode::$mode,
1195 );
1196 builder = builder.version(maj, min, patch).gate_decision($crate::policy::TransitStatus::$gate);
1197 $(
1198 builder = builder.tag_filter(vec![ $( $tag ),* ]);
1199 )?
1200 $(
1201 if let Some(desc) = $desc {
1202 builder = builder.description(desc);
1203 }
1204 )?
1205 if let Some(desc) = $desc_opt {
1207 builder = builder.description(desc);
1208 }
1209 builder
1210 }};
1211}
1212
1213#[doc(hidden)]
1215#[macro_export]
1216macro_rules! __tb_scenario_verify_impl {
1217 (
1219 single_spec: $spec:ty,
1220 trace: $trace:expr,
1221 $(csp: $csp:ty,)?
1222 $(fdr: $fdr_config:expr,)?
1223 $(hooks: {
1224 $(on_pass: $on_pass:expr,)?
1225 $(on_fail: $on_fail:expr)?
1226 },)?
1227 ) => {{
1228 let spec = <$spec>::latest();
1229 let l1_result = $crate::testing::specs::verify_trace(spec, &$trace);
1230
1231 let mut scenario_result = $crate::testing::ScenarioResult::default();
1233 scenario_result.trace = $trace;
1235 scenario_result.assert_spec = Some(spec.clone());
1237 scenario_result.spec_violation = l1_result.as_ref().err().cloned();
1239
1240 let l1_passed = l1_result.is_ok();
1241
1242 $crate::__tb_scenario_verify_impl! {
1244 @common
1245 l1_passed: l1_passed,
1246 scenario_result: scenario_result,
1247 $(csp: $csp,)?
1248 $(fdr: $fdr_config,)?
1249 $(hooks: {
1250 $(on_pass: $on_pass,)?
1251 $(on_fail: $on_fail)?
1252 },)?
1253 }
1254 }};
1255
1256 (
1258 multi_specs: $specs:expr,
1259 trace: $trace:expr,
1260 $(csp: $csp:ty,)?
1261 $(fdr: $fdr_config:expr,)?
1262 $(hooks: {
1263 $(on_pass: $on_pass:expr,)?
1264 $(on_fail: $on_fail:expr)?
1265 },)?
1266 ) => {{
1267 let mut all_passed = true;
1268 let mut first_violation = None;
1269
1270 for spec in &$specs {
1272 let verification_result = $crate::testing::specs::verify_trace(*spec, &$trace);
1273 if let Err(v) = verification_result {
1274 all_passed = false;
1275 if first_violation.is_none() {
1276 first_violation = Some(v);
1277 }
1278 }
1279 }
1280
1281 let mut scenario_result = $crate::testing::ScenarioResult::default();
1283
1284 scenario_result.trace = $trace;
1286 scenario_result.assert_specs = $specs.iter().map(|s| (*s).clone()).collect();
1288 scenario_result.spec_violation = first_violation;
1289
1290 $crate::__tb_scenario_verify_impl! {
1292 @common
1293 l1_passed: all_passed,
1294 scenario_result: scenario_result,
1295 $(csp: $csp,)?
1296 $(fdr: $fdr_config,)?
1297 $(hooks: {
1298 $(on_pass: $on_pass,)?
1299 $(on_fail: $on_fail)?
1300 },)?
1301 }
1302 }};
1303
1304 (
1306 @common
1307 l1_passed: $l1_passed:expr,
1308 scenario_result: $scenario_result:expr,
1309 $(csp: $csp:ty,)?
1310 $(fdr: $fdr_config:expr,)?
1311 $(hooks: {
1312 $(on_pass: $on_pass:expr,)?
1313 $(on_fail: $on_fail:expr)?
1314 },)?
1315 ) => {{
1316 let mut scenario_result = $scenario_result;
1317 let l1_passed = $l1_passed;
1318
1319 #[allow(unused_mut, unused_assignments)]
1321 let mut csp_failed = false;
1322
1323 #[cfg(feature = "testing-csp")]
1324 {
1325 $(
1326 let csp_spec = <$csp>::default();
1327 let csp_result = <$csp as $crate::testing::specs::csp::ProcessSpec>::validate_trace(&csp_spec, &scenario_result.trace);
1328 csp_failed = !csp_result.valid;
1329 scenario_result.csp_result = Some(csp_result);
1330 scenario_result.process = Some(<$csp>::process());
1332
1333 #[cfg(feature = "testing-timing")]
1335 {
1336 let process = scenario_result.process.as_ref().unwrap();
1337 scenario_result.timing_constraints = process.timing_constraints.clone();
1338 }
1339 )?
1340 }
1341
1342 #[allow(unused_mut, unused_assignments)]
1344 let mut fdr_failed = false;
1345 #[allow(unused_mut, unused_assignments)]
1346 let mut expect_failure = false;
1347
1348 #[cfg(feature = "testing-fdr")]
1349 {
1350 $(
1351 use $crate::testing::fdr::{DefaultFdrExplorer, FdrConfig};
1352 let config: FdrConfig = $fdr_config.into();
1353 expect_failure = config.expect_failure;
1354
1355 #[cfg(feature = "testing-fault")]
1359 let process_to_explore = if config.fault_model.is_some() && !config.specs.is_empty() {
1360 &config.specs[0]
1361 } else {
1362 &scenario_result.trace.to_process()
1363 };
1364
1365 #[cfg(not(feature = "testing-fault"))]
1366 let process_to_explore = &scenario_result.trace.to_process();
1367
1368 let mut explorer = DefaultFdrExplorer::with_defaults(process_to_explore, config.clone());
1369 let verdict = explorer.explore();
1370 fdr_failed = !verdict.passed;
1371 scenario_result.fdr_verdict = Some(verdict);
1372 )?
1373 }
1374
1375 scenario_result.passed = l1_passed && !csp_failed && (!fdr_failed || expect_failure);
1377
1378 $crate::__tb_scenario_call_hooks!(
1380 scenario_result: scenario_result,
1381 csp_failed: csp_failed,
1382 fdr_failed: fdr_failed,
1383 expect_failure: expect_failure,
1384 $(hooks: {
1385 $(on_pass: $on_pass,)?
1386 $(on_fail: $on_fail)?
1387 },)?
1388 )
1389 }};
1390}