Skip to main content

rustdv_methodology/
error.rs

1//! Test failure value.
2//!
3//! `Err` fails the test (design-doc §0.6): `Result` for *checks*, panics for
4//! *testbench bugs* (§7.3 failure taxonomy).
5//!
6//! This lived in `rustdv-runner` until step 4. `Component::run` returns it
7//! (D46/D47), and `rustdv-methodology` sits below the runner, so the type had to
8//! move down. `rustdv-runner` re-exports it, and `::rustdv::TestError`
9//! resolves exactly as before.
10//!
11//! **The `kind` carries a machine-readable cause** so a test can declare
12//! `#[rustdv::test(expect_error = "config_not_found")]` and pass only if it
13//! fails *that* way (D68). Without it the runner sees a flat string and
14//! `expect_fail` accepts any failure at all — including a panic from
15//! somewhere unrelated. pyuvm gets this from exception *types*
16//! (`expect_error=UVMConfigItemNotFound`); rustdv errors are values, so the
17//! cause travels as a field.
18
19use std::fmt;
20
21use rustdv_sim::executor::TaskError;
22use rustdv_sim::{HandleError, ValueError};
23
24use crate::config::ConfigError;
25use crate::sequence::SeqError;
26
27#[derive(Debug, Clone)]
28pub struct TestError {
29    msg: String,
30    kind: Option<&'static str>,
31}
32
33impl TestError {
34    /// An unclassified failure — the common case for a check that failed.
35    pub fn new(msg: impl Into<String>) -> TestError {
36        TestError {
37            msg: msg.into(),
38            kind: None,
39        }
40    }
41
42    /// A failure with a cause the runner can match against `expect_error`.
43    /// Kinds are stable strings; see [`crate::config::ConfigError::kind`].
44    pub fn with_kind(msg: impl Into<String>, kind: &'static str) -> TestError {
45        TestError {
46            msg: msg.into(),
47            kind: Some(kind),
48        }
49    }
50
51    pub fn message(&self) -> &str {
52        &self.msg
53    }
54
55    /// The machine-readable cause, if this failure has one.
56    pub fn kind(&self) -> Option<&'static str> {
57        self.kind
58    }
59}
60
61impl fmt::Display for TestError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", self.msg)
64    }
65}
66impl std::error::Error for TestError {}
67
68impl From<&str> for TestError {
69    fn from(s: &str) -> Self {
70        TestError::new(s)
71    }
72}
73impl From<String> for TestError {
74    fn from(s: String) -> Self {
75        TestError::new(s)
76    }
77}
78impl From<HandleError> for TestError {
79    fn from(e: HandleError) -> Self {
80        TestError::new(e.to_string())
81    }
82}
83impl From<ValueError> for TestError {
84    fn from(e: ValueError) -> Self {
85        TestError::new(e.to_string())
86    }
87}
88impl From<SeqError> for TestError {
89    fn from(e: SeqError) -> Self {
90        TestError::new(e.to_string())
91    }
92}
93impl From<TaskError> for TestError {
94    fn from(e: TaskError) -> Self {
95        TestError::new(e.to_string())
96    }
97}
98
99/// Configuration failures keep their cause, so `?` in a test body still
100/// lets `expect_error` distinguish "nothing was set" from "wrong type".
101impl From<ConfigError> for TestError {
102    fn from(e: ConfigError) -> Self {
103        TestError::with_kind(e.to_string(), e.kind())
104    }
105}