Skip to main content

tesults_test/
lib.rs

1pub use tesults_test_macros::test;
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::sync::Mutex;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8thread_local! {
9    static PENDING_DESC:   RefCell<String>                  = RefCell::new(String::new());
10    static PENDING_STEPS:  RefCell<Vec<tesults::Step>>      = RefCell::new(Vec::new());
11    static PENDING_CUSTOM: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
12    static PENDING_FILES:  RefCell<Vec<String>>             = RefCell::new(Vec::new());
13}
14
15static RESULTS: Mutex<Vec<tesults::Case>> = Mutex::new(Vec::new());
16
17/// Set a description for the currently-running test.
18pub fn description(desc: &str) {
19    PENDING_DESC.with(|d| *d.borrow_mut() = desc.to_string());
20}
21
22/// Attach a custom key-value field to the currently-running test.
23pub fn custom(key: &str, value: &str) {
24    PENDING_CUSTOM.with(|c| {
25        c.borrow_mut().insert(key.to_string(), value.to_string());
26    });
27}
28
29/// Record a step for the currently-running test.
30/// `result` should be `"pass"` or `"fail"`.
31pub fn step(name: &str, result: &str, desc: &str, reason: &str) {
32    PENDING_STEPS.with(|s| {
33        s.borrow_mut().push(tesults::Step {
34            name: name.to_string(),
35            result: result.to_string(),
36            desc: desc.to_string(),
37            reason: reason.to_string(),
38        });
39    });
40}
41
42/// Attach a file path to the currently-running test (uploaded to Tesults).
43pub fn file(path: &str) {
44    PENDING_FILES.with(|f| f.borrow_mut().push(path.to_string()));
45}
46
47#[ctor::dtor]
48fn upload_on_exit() {
49    let cases: Vec<tesults::Case> = {
50        let mut guard = match RESULTS.lock() {
51            Ok(g) => g,
52            Err(e) => e.into_inner(),
53        };
54        std::mem::take(&mut *guard)
55    };
56
57    if cases.is_empty() {
58        return;
59    }
60
61    let target_raw = match std::env::var("TESULTS_TARGET") {
62        Ok(v) if !v.is_empty() => v,
63        _ => return,
64    };
65
66    let target = lookup_config(&target_raw);
67
68    let data = tesults::Data {
69        target,
70        cases,
71        integration_name: String::from("tesults-test"),
72        integration_version: String::from(env!("CARGO_PKG_VERSION")),
73        test_framework: String::from("rust"),
74    };
75
76    eprintln!("tesults-test: uploading results...");
77    let response = tesults::upload(data);
78    eprintln!("tesults-test: success: {}", response.success);
79    eprintln!("tesults-test: message: {}", response.message);
80    eprintln!("tesults-test: warnings: {:?}", response.warnings);
81    eprintln!("tesults-test: errors: {:?}", response.errors);
82}
83
84fn lookup_config(key: &str) -> String {
85    if let Ok(config_path) = std::env::var("TESULTS_CONFIG") {
86        if let Ok(content) = std::fs::read_to_string(&config_path) {
87            for line in content.lines() {
88                let line = line.trim();
89                if line.starts_with('#') || line.is_empty() {
90                    continue;
91                }
92                if let Some((k, v)) = line.split_once('=') {
93                    if k.trim() == key {
94                        return v.trim().to_string();
95                    }
96                }
97            }
98        }
99    }
100    key.to_string()
101}
102
103pub mod __private {
104    use super::*;
105
106    pub fn now_ms() -> i64 {
107        SystemTime::now()
108            .duration_since(UNIX_EPOCH)
109            .map(|d| d.as_millis() as i64)
110            .unwrap_or(0)
111    }
112
113    pub fn record_result(
114        name: &str,
115        module: &str,
116        passed: bool,
117        reason: String,
118        start: i64,
119        end: i64,
120    ) {
121        let desc = PENDING_DESC.with(|d| d.borrow().clone());
122        let steps = PENDING_STEPS.with(|s| s.borrow().clone());
123        let custom = PENDING_CUSTOM.with(|c| c.borrow().clone());
124        let files = PENDING_FILES.with(|f| f.borrow().clone());
125
126        PENDING_DESC.with(|d| d.borrow_mut().clear());
127        PENDING_STEPS.with(|s| s.borrow_mut().clear());
128        PENDING_CUSTOM.with(|c| c.borrow_mut().clear());
129        PENDING_FILES.with(|f| f.borrow_mut().clear());
130
131        let suite = module.split("::").last().unwrap_or(module).to_string();
132
133        let case = tesults::Case {
134            name: name.to_string(),
135            result: if passed {
136                String::from("pass")
137            } else {
138                String::from("fail")
139            },
140            suite,
141            desc,
142            reason,
143            start,
144            end,
145            files,
146            custom,
147            steps,
148            ..Default::default()
149        };
150
151        if let Ok(mut guard) = RESULTS.lock() {
152            guard.push(case);
153        }
154    }
155}