Skip to main content

leptos_browser_test/
error.rs

1use std::{path::PathBuf, time::Duration};
2
3use thiserror::Error;
4
5use crate::CargoLeptosMode;
6
7/// Diagnostic context attached to startup-failure variants.
8///
9/// Carries the descriptive app name, the stdout fragment we were waiting for, and the most
10/// recent stdout/stderr lines captured before the failure was reported. The tails are bounded
11/// by [`LeptosTestAppConfig::with_startup_log_tail_lines`](crate::LeptosTestAppConfig::with_startup_log_tail_lines).
12#[derive(Debug, PartialEq, Eq)]
13pub struct StartupFailureContext {
14    /// The descriptive app name.
15    pub app_name: String,
16    /// The expected startup line fragment.
17    pub expected_line: String,
18    /// Recent stdout lines.
19    pub stdout_tail: String,
20    /// Recent stderr lines.
21    pub stderr_tail: String,
22}
23
24/// Error contexts reported by Leptos test app startup operations.
25#[derive(Debug, PartialEq, Eq, Error)]
26pub enum LeptosBrowserTestError {
27    /// The configured test app directory could not be resolved.
28    #[error("failed to resolve {app_name} directory {app_dir:?}")]
29    ResolveAppDir {
30        /// The descriptive app name.
31        app_name: String,
32        /// The configured app directory.
33        app_dir: PathBuf,
34    },
35
36    /// The `cargo leptos` process could not be spawned.
37    #[error("failed to spawn `cargo leptos {mode_arg}` for {app_name}", mode_arg = mode.as_arg())]
38    SpawnCargoLeptos {
39        /// The descriptive app name.
40        app_name: String,
41        /// The selected `cargo leptos` mode.
42        mode: CargoLeptosMode,
43    },
44
45    /// A free site port could not be selected.
46    #[error("failed to find a free site port for {app_name}")]
47    FindFreeSitePort {
48        /// The descriptive app name.
49        app_name: String,
50    },
51
52    /// A free reload port could not be selected.
53    #[error("failed to find a free reload port for {app_name}")]
54    FindFreeReloadPort {
55        /// The descriptive app name.
56        app_name: String,
57    },
58
59    /// The frontend stdout stream closed before startup completed.
60    #[error(
61        "{app_name} stdout closed before startup completed. Expected stdout to contain {expected_line:?}.\n\nRecent stdout:\n{stdout_tail}\n\nRecent stderr:\n{stderr_tail}",
62        app_name = .0.app_name,
63        expected_line = .0.expected_line,
64        stdout_tail = .0.stdout_tail,
65        stderr_tail = .0.stderr_tail,
66    )]
67    StartupStdoutClosed(StartupFailureContext),
68
69    /// The frontend did not produce the expected startup line before the timeout.
70    #[error(
71        "{app_name} did not start within {timeout:?} ({reason}); expected stdout to contain {expected_line:?}.\n\nRecent stdout:\n{stdout_tail}\n\nRecent stderr:\n{stderr_tail}",
72        app_name = ctx.app_name,
73        expected_line = ctx.expected_line,
74        stdout_tail = ctx.stdout_tail,
75        stderr_tail = ctx.stderr_tail,
76    )]
77    StartupTimedOut {
78        /// Diagnostic context (app name, expected line, captured tails).
79        ctx: StartupFailureContext,
80        /// The configured startup timeout.
81        timeout: Duration,
82        /// The caller-supplied reason that justified this particular timeout, recorded by
83        /// [`with_startup_timeout`](crate::LeptosTestAppConfig::with_startup_timeout).
84        reason: String,
85    },
86
87    /// Reading the frontend stdout stream failed before the startup line was observed.
88    #[error(
89        "{app_name} failed to read stdout while waiting for {expected_line:?}.\n\nRecent stdout:\n{stdout_tail}\n\nRecent stderr:\n{stderr_tail}",
90        app_name = .0.app_name,
91        expected_line = .0.expected_line,
92        stdout_tail = .0.stdout_tail,
93        stderr_tail = .0.stderr_tail,
94    )]
95    StreamRead(StartupFailureContext),
96
97    /// The configured site address could not be parsed as a `host:port` socket address.
98    #[error("invalid site_addr {site_addr:?} for {app_name}: expected `host:port`")]
99    InvalidSiteAddr {
100        /// The descriptive app name.
101        app_name: String,
102        /// The supplied site address that failed to parse.
103        site_addr: String,
104    },
105}
106
107#[cfg(test)]
108mod tests {
109    use std::time::Duration;
110
111    use assertr::prelude::*;
112
113    use super::{LeptosBrowserTestError, StartupFailureContext};
114
115    fn ctx() -> StartupFailureContext {
116        StartupFailureContext {
117            app_name: "demo".to_owned(),
118            expected_line: "listening on".to_owned(),
119            stdout_tail: "out-line".to_owned(),
120            stderr_tail: "err-line".to_owned(),
121        }
122    }
123
124    #[test]
125    fn startup_stdout_closed_display_matches_documented_format() {
126        let err = LeptosBrowserTestError::StartupStdoutClosed(ctx());
127        assert_that!(err.to_string()).is_equal_to(
128            "demo stdout closed before startup completed. Expected stdout to contain \"listening on\".\n\nRecent stdout:\nout-line\n\nRecent stderr:\nerr-line"
129                .to_owned(),
130        );
131    }
132
133    #[test]
134    fn startup_timed_out_display_matches_documented_format() {
135        let err = LeptosBrowserTestError::StartupTimedOut {
136            ctx: ctx(),
137            timeout: Duration::from_secs(7),
138            reason: "tight bound for unit-style smoke test".to_owned(),
139        };
140        assert_that!(err.to_string()).is_equal_to(
141            "demo did not start within 7s (tight bound for unit-style smoke test); expected stdout to contain \"listening on\".\n\nRecent stdout:\nout-line\n\nRecent stderr:\nerr-line"
142                .to_owned(),
143        );
144    }
145
146    #[test]
147    fn stream_read_display_matches_documented_format() {
148        let err = LeptosBrowserTestError::StreamRead(ctx());
149        assert_that!(err.to_string()).is_equal_to(
150            "demo failed to read stdout while waiting for \"listening on\".\n\nRecent stdout:\nout-line\n\nRecent stderr:\nerr-line"
151                .to_owned(),
152        );
153    }
154}