browser_commander/browser/
browser_process.rs1use std::fmt;
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::Arc;
14use std::time::Duration;
15
16use crate::utilities::ManagedProcess;
17
18pub(crate) type ExitFuture = Pin<Box<dyn Future<Output = i32> + Send + 'static>>;
20
21pub(crate) trait ProcessControl: Send + Sync {
24 fn pid(&self) -> Option<u32>;
25 fn exit_code(&self) -> Option<i32>;
26 fn kill(&self) -> bool;
27 fn exited(&self) -> ExitFuture;
28}
29
30impl ProcessControl for ManagedProcess {
31 fn pid(&self) -> Option<u32> {
32 ManagedProcess::pid(self)
33 }
34
35 fn exit_code(&self) -> Option<i32> {
36 ManagedProcess::exit_code(self)
37 }
38
39 fn kill(&self) -> bool {
40 ManagedProcess::kill(self)
41 }
42
43 fn exited(&self) -> ExitFuture {
44 Box::pin(ManagedProcess::exited(self))
45 }
46}
47
48#[async_trait::async_trait]
51pub(crate) trait BrowserCloser: Send + Sync {
52 async fn close(&self) -> anyhow::Result<()>;
53}
54
55#[derive(Clone)]
62pub struct BrowserProcess {
63 inner: Arc<dyn ProcessControl>,
64}
65
66impl BrowserProcess {
67 pub(crate) fn from_control(inner: Arc<dyn ProcessControl>) -> Self {
68 Self { inner }
69 }
70
71 pub(crate) fn from_managed(process: ManagedProcess) -> Self {
72 Self::from_control(Arc::new(process))
73 }
74
75 pub fn id(&self) -> u32 {
77 self.pid().unwrap_or(0)
78 }
79
80 pub fn pid(&self) -> Option<u32> {
82 self.inner.pid()
83 }
84
85 pub fn exit_code(&self) -> Option<i32> {
87 self.inner.exit_code()
88 }
89
90 pub fn is_running(&self) -> bool {
92 self.exit_code().is_none()
93 }
94
95 pub fn kill(&self) -> bool {
99 if !self.is_running() {
100 return false;
101 }
102 self.inner.kill()
103 }
104
105 pub async fn wait(&self) -> i32 {
107 self.exited().await
108 }
109
110 pub async fn wait_timeout(&self, timeout: Duration) -> Option<i32> {
113 if let Some(code) = self.exit_code() {
114 return Some(code);
115 }
116 tokio::time::timeout(timeout, self.exited()).await.ok()
117 }
118
119 pub fn exited(&self) -> impl Future<Output = i32> + Send + 'static {
121 self.inner.exited()
122 }
123}
124
125impl fmt::Debug for BrowserProcess {
126 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127 formatter
128 .debug_struct("BrowserProcess")
129 .field("pid", &self.pid())
130 .field("exit_code", &self.exit_code())
131 .finish()
132 }
133}
134
135#[cfg(test)]
136pub(crate) mod fake {
137 use super::{BrowserProcess, ExitFuture, ProcessControl};
140 use std::sync::atomic::{AtomicUsize, Ordering};
141 use std::sync::Arc;
142 use tokio::sync::watch;
143
144 pub(crate) struct FakeProcess {
146 exit: watch::Sender<Option<i32>>,
147 pub(crate) kills: AtomicUsize,
148 pub(crate) exits_on_kill: bool,
150 }
151
152 impl FakeProcess {
153 pub(crate) fn new() -> Arc<Self> {
154 Arc::new(Self {
155 exit: watch::channel(None).0,
156 kills: AtomicUsize::new(0),
157 exits_on_kill: true,
158 })
159 }
160
161 pub(crate) fn exit(&self, code: i32) {
162 self.exit.send_replace(Some(code));
163 }
164
165 pub(crate) fn kill_count(&self) -> usize {
166 self.kills.load(Ordering::SeqCst)
167 }
168
169 pub(crate) fn handle(self: &Arc<Self>) -> BrowserProcess {
170 BrowserProcess::from_control(Arc::clone(self) as Arc<dyn ProcessControl>)
171 }
172 }
173
174 impl ProcessControl for FakeProcess {
175 fn pid(&self) -> Option<u32> {
176 Some(4242)
177 }
178
179 fn exit_code(&self) -> Option<i32> {
180 *self.exit.borrow()
181 }
182
183 fn kill(&self) -> bool {
184 self.kills.fetch_add(1, Ordering::SeqCst);
185 if self.exits_on_kill {
186 self.exit(143);
187 }
188 true
189 }
190
191 fn exited(&self) -> ExitFuture {
192 let mut exit = self.exit.subscribe();
193 Box::pin(async move {
194 match exit.wait_for(Option::is_some).await {
195 Ok(code) => code.unwrap_or(1),
196 Err(_) => 1,
197 }
198 })
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::utilities::{start_process, StartProcessOptions};
207
208 #[tokio::test]
209 async fn wraps_a_managed_process() {
210 let process = start_process("sleep", &["30"], StartProcessOptions::default())
211 .await
212 .unwrap();
213 let browser = BrowserProcess::from_managed(process);
214 assert!(browser.is_running());
215 assert_ne!(browser.id(), 0);
216 assert!(browser
217 .wait_timeout(Duration::from_millis(50))
218 .await
219 .is_none());
220 assert!(browser.kill());
221 let code = browser.wait_timeout(Duration::from_secs(5)).await;
222 assert_eq!(code, Some(128 + 15));
223 assert!(!browser.kill());
224 }
225
226 #[tokio::test]
227 async fn fake_process_exits_when_killed() {
228 let fake = fake::FakeProcess::new();
229 let process = fake.handle();
230 let exited = process.exited();
231 process.kill();
232 assert_eq!(exited.await, 143);
233 assert_eq!(fake.kill_count(), 1);
234 }
235}