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
17pub fn description(desc: &str) {
19 PENDING_DESC.with(|d| *d.borrow_mut() = desc.to_string());
20}
21
22pub 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
29pub 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
42pub 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}