xianyun_deploy/diagnostics/
mod.rs1#![allow(dead_code)]
6
7pub mod base;
8pub mod builtin;
9
10use std::sync::Arc;
11use std::sync::mpsc::{self, Receiver};
12
13use base::{Diagnostic, Meta};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct DiagnosticResult {
18 pub name: String,
19 pub output: String,
20 pub err: Option<String>,
21 pub skipped: bool,
22}
23
24pub struct Runner {
26 diagnostics: Arc<Vec<Arc<dyn Diagnostic>>>,
27}
28
29impl Runner {
30 #[must_use]
32 pub fn new(diagnostics: Vec<Arc<dyn Diagnostic>>) -> Self {
33 Self {
34 diagnostics: Arc::new(diagnostics),
35 }
36 }
37
38 #[must_use]
42 pub fn run(&self, meta: Meta, is_cluster_up: bool) -> Receiver<DiagnosticResult> {
43 let (tx, rx) = mpsc::channel();
44 let diagnostics = Arc::clone(&self.diagnostics);
45 let meta = Arc::new(meta);
46
47 std::thread::spawn(move || {
48 let handles: Vec<_> = diagnostics
49 .iter()
50 .cloned()
51 .map(|d| {
52 let tx = tx.clone();
53 let meta = Arc::clone(&meta);
54
55 std::thread::spawn(move || {
56 if d.needs_cluster() && !is_cluster_up {
57 let _ = tx.send(DiagnosticResult {
58 name: d.name().to_string(),
59 output: String::new(),
60 err: None,
61 skipped: true,
62 });
63
64 return;
65 }
66
67 let (output, err) = d.run(&meta);
68 let _ = tx.send(DiagnosticResult {
69 name: d.name().to_string(),
70 output,
71 err: err.map(|e| e.to_string()),
72 skipped: false,
73 });
74 })
75 })
76 .collect();
77
78 for handle in handles {
79 let _ = handle.join();
80 }
81 });
82
83 rx
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use std::sync::Arc;
90 use std::sync::atomic::{AtomicI64, Ordering};
91 use std::time::Duration;
92
93 use super::*;
94 use base::{Meta, SimpleDiagnostic};
95
96 #[test]
98 fn test_runner() {
99 let skipped_calls = Arc::new(AtomicI64::new(0));
100
101 let example_diagnostics: Vec<Arc<dyn Diagnostic>> = vec![
102 Arc::new(SimpleDiagnostic::new("simple diag", false, |_meta| {
103 (String::new(), None)
104 })),
105 Arc::new({
106 let skipped_calls = Arc::clone(&skipped_calls);
107 SimpleDiagnostic::new("needs cluster!!", true, move |_meta| {
108 skipped_calls.fetch_add(1, Ordering::SeqCst);
109 (String::new(), None)
110 })
111 }),
112 Arc::new(SimpleDiagnostic::new("omg warning", false, |_meta| {
113 (
114 "woof, something is wrong, here is how you fix it".to_string(),
115 None,
116 )
117 })),
118 Arc::new(SimpleDiagnostic::new("ah well", false, |_meta| {
119 (
120 String::new(),
121 Some(
122 Box::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
123 as Box<dyn std::error::Error + Send + Sync>,
124 ),
125 )
126 })),
127 ];
128
129 let runner = Runner::new(example_diagnostics);
130 let rx = runner.run(Meta::default(), false);
131
132 let mut results = Vec::new();
133 let deadline = std::time::Instant::now() + Duration::from_millis(500);
134 while std::time::Instant::now() < deadline {
135 match rx.recv_timeout(Duration::from_millis(10)) {
136 Ok(r) => results.push(r),
137 Err(mpsc::RecvTimeoutError::Timeout) => continue,
138 Err(mpsc::RecvTimeoutError::Disconnected) => break,
139 }
140 }
141
142 results.sort_by(|a, b| a.name.cmp(&b.name));
143
144 let mut expected = vec![
145 DiagnosticResult {
146 name: "simple diag".to_string(),
147 output: String::new(),
148 err: None,
149 skipped: false,
150 },
151 DiagnosticResult {
152 name: "needs cluster!!".to_string(),
153 output: String::new(),
154 err: None,
155 skipped: true,
156 },
157 DiagnosticResult {
158 name: "omg warning".to_string(),
159 output: "woof, something is wrong, here is how you fix it".to_string(),
160 err: None,
161 skipped: false,
162 },
163 DiagnosticResult {
164 name: "ah well".to_string(),
165 output: String::new(),
166 err: Some("permission denied".to_string()),
167 skipped: false,
168 },
169 ];
170 expected.sort_by(|a, b| a.name.cmp(&b.name));
171
172 assert_eq!(results, expected);
173 assert_eq!(
174 skipped_calls.load(Ordering::SeqCst),
175 0,
176 "we must not have called the skipped diagnostic"
177 );
178 }
179}