Skip to main content

smtp_test_tool/
runner.rs

1//! High-level orchestrator: run whichever of SMTP / IMAP / POP3 are enabled.
2
3use crate::config::Profile;
4use crate::{imap, pop3, smtp};
5use serde::{Deserialize, Serialize};
6use std::time::Instant;
7use tracing::{error, info};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum TestOutcome {
11    Pass,
12    Fail,
13    Skipped,
14}
15
16impl TestOutcome {
17    pub fn as_tag(self) -> &'static str {
18        match self {
19            TestOutcome::Pass => "[ PASS ]",
20            TestOutcome::Fail => "[ FAIL ]",
21            TestOutcome::Skipped => "[ SKIP ]",
22        }
23    }
24}
25
26#[derive(Debug, Default, Clone, Serialize, Deserialize)]
27pub struct TestResults {
28    pub smtp: Option<TestOutcome>,
29    pub imap: Option<TestOutcome>,
30    pub pop3: Option<TestOutcome>,
31    pub elapsed_ms: u128,
32}
33
34impl TestResults {
35    pub fn all_passed(&self) -> bool {
36        let xs = [self.smtp, self.imap, self.pop3];
37        let any_run = xs
38            .iter()
39            .any(|o| matches!(o, Some(TestOutcome::Pass | TestOutcome::Fail)));
40        any_run && xs.iter().all(|o| !matches!(o, Some(TestOutcome::Fail)))
41    }
42}
43
44/// Run every enabled protocol test on the given profile.
45pub fn run_tests(p: &Profile) -> TestResults {
46    let t0 = Instant::now();
47    let mut r = TestResults::default();
48
49    if p.smtp_enabled {
50        r.smtp = Some(run_one("SMTP", || smtp::run(p)));
51    } else {
52        r.smtp = Some(TestOutcome::Skipped);
53    }
54    if p.imap_enabled {
55        r.imap = Some(run_one("IMAP", || imap::run(p)));
56    } else {
57        r.imap = Some(TestOutcome::Skipped);
58    }
59    if p.pop_enabled {
60        r.pop3 = Some(run_one("POP3", || pop3::run(p)));
61    } else {
62        r.pop3 = Some(TestOutcome::Skipped);
63    }
64    r.elapsed_ms = t0.elapsed().as_millis();
65
66    info!("===== SUMMARY  ({} ms) =====", r.elapsed_ms);
67    log_outcome("SMTP", r.smtp);
68    log_outcome("IMAP", r.imap);
69    log_outcome("POP3", r.pop3);
70    r
71}
72
73fn run_one<F>(name: &str, f: F) -> TestOutcome
74where
75    F: FnOnce() -> anyhow::Result<bool>,
76{
77    match f() {
78        Ok(true) => TestOutcome::Pass,
79        Ok(false) => TestOutcome::Fail,
80        Err(e) => {
81            error!("{name} aborted: {e:#}");
82            TestOutcome::Fail
83        }
84    }
85}
86
87fn log_outcome(name: &str, o: Option<TestOutcome>) {
88    match o {
89        Some(TestOutcome::Pass) => info!("  {}  {name}", TestOutcome::Pass.as_tag()),
90        Some(TestOutcome::Fail) => error!("  {}  {name}", TestOutcome::Fail.as_tag()),
91        Some(TestOutcome::Skipped) => info!("  {}  {name}", TestOutcome::Skipped.as_tag()),
92        None => {}
93    }
94}