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 { msg: msg.into(), kind: None }
37    }
38
39    /// A failure with a cause the runner can match against `expect_error`.
40    /// Kinds are stable strings; see [`crate::config::ConfigError::kind`].
41    pub fn with_kind(msg: impl Into<String>, kind: &'static str) -> TestError {
42        TestError { msg: msg.into(), kind: Some(kind) }
43    }
44
45    pub fn message(&self) -> &str {
46        &self.msg
47    }
48
49    /// The machine-readable cause, if this failure has one.
50    pub fn kind(&self) -> Option<&'static str> {
51        self.kind
52    }
53}
54
55impl fmt::Display for TestError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "{}", self.msg)
58    }
59}
60impl std::error::Error for TestError {}
61
62impl From<&str> for TestError {
63    fn from(s: &str) -> Self {
64        TestError::new(s)
65    }
66}
67impl From<String> for TestError {
68    fn from(s: String) -> Self {
69        TestError::new(s)
70    }
71}
72impl From<HandleError> for TestError {
73    fn from(e: HandleError) -> Self {
74        TestError::new(e.to_string())
75    }
76}
77impl From<ValueError> for TestError {
78    fn from(e: ValueError) -> Self {
79        TestError::new(e.to_string())
80    }
81}
82impl From<SeqError> for TestError {
83    fn from(e: SeqError) -> Self {
84        TestError::new(e.to_string())
85    }
86}
87impl From<TaskError> for TestError {
88    fn from(e: TaskError) -> Self {
89        TestError::new(e.to_string())
90    }
91}
92
93/// Configuration failures keep their cause, so `?` in a test body still
94/// lets `expect_error` distinguish "nothing was set" from "wrong type".
95impl From<ConfigError> for TestError {
96    fn from(e: ConfigError) -> Self {
97        TestError::with_kind(e.to_string(), e.kind())
98    }
99}