test_notifier/
lib.rs

1#![feature(async_closure)]
2
3use lazy_static::lazy_static;
4use nustify::Builder;
5use std::env;
6
7use tokio::task;
8use tokio::task::spawn_blocking;
9
10lazy_static! {
11    static ref KEY: String = env::var("IFTTT_TEST_WEBHOOK_KEY").unwrap();
12}
13
14pub struct TestNotifier {
15    message: Option<String>,
16}
17
18impl TestNotifier {
19    pub fn new() -> TestNotifier {
20        TestNotifier { message: None }
21    }
22    pub fn set_message(&mut self, message: String) {
23        self.message = Some(message);
24    }
25    pub fn new_with_message(message: String) -> TestNotifier {
26        TestNotifier {
27            message: Some(message),
28        }
29    }
30}
31
32impl Default for TestNotifier {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38async fn do_notify(test_name: String, extra_message: Option<String>) {
39    println!("test_name: {test_name}");
40    println!("message: {:?}", extra_message.clone());
41    let extra_message = extra_message.unwrap_or_else(|| "no extra message".to_string());
42    let output = format!(
43        "test {} is done. also, my caller says: {}",
44        test_name,
45        extra_message.clone()
46    );
47    let notification = Builder::new(output.clone())
48        .title("Hello from Rust test notifier".to_owned())
49        .build();
50    nustify::send(&notification, "nustify", &KEY).await.unwrap();
51}
52
53impl Drop for TestNotifier {
54    fn drop(&mut self) {
55        let mut found_my_drop = false; // says "hey i found where i am being dropped" as a bookmark to find which test we were in
56        let mut printed_my_message = false; // short-circuits the backtrace after finding which test we were in
57        backtrace::trace(|frame| {
58            backtrace::resolve_frame(frame, |symbol| {
59                if !printed_my_message {
60                    let sn = symbol
61                        .name()
62                        .map_or_else(|| "unknown".to_string(), |sn| format!("{sn:?}"));
63                    // println!("symbol: {:?}", sn);
64                    if sn.contains("core::ptr::drop_in_place<test_notifier::TestNotifier>") {
65                        found_my_drop = true;
66                    } else if found_my_drop {
67                        let msg = self.message.clone();
68                        let rt = tokio::runtime::Runtime::new().unwrap();
69                        let local = task::LocalSet::new();
70                        local.block_on(&rt, async {
71                            let join = task::spawn_local(async move {
72                                spawn_blocking(move || do_notify(sn, msg))
73                                    .await
74                                    .unwrap()
75                                    .await;
76                            });
77                            join.await.unwrap();
78                        });
79                        printed_my_message = true;
80                    }
81                }
82            });
83            true
84        });
85    }
86}
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_failing() {
93        let mut tn = TestNotifier::new();
94        tn.set_message("about to fail".to_string());
95        assert!(false);
96    }
97
98    #[test]
99    fn test_passing() {
100        let mut tn = TestNotifier::new();
101        tn.set_message("about to pass".to_string());
102        assert!(true);
103    }
104}