yash_env/input/
reporter.rs1use super::{Context, Input, Result};
18use crate::Env;
19use crate::io::Fd;
20use crate::job::fmt::Accumulator;
21use crate::option::{Interactive, Monitor, Off};
22use crate::system::{Fcntl, Signals, Write};
23use std::cell::RefCell;
24
25#[derive(Debug)]
31pub struct Reporter<'a, 'b, S, T> {
32 inner: T,
33 env: &'a RefCell<&'b mut Env<S>>,
34}
35
36impl<'a, 'b, S, T> Reporter<'a, 'b, S, T> {
37 pub fn new(inner: T, env: &'a RefCell<&'b mut Env<S>>) -> Self {
45 Self { inner, env }
46 }
47}
48
49impl<S, T: Clone> Clone for Reporter<'_, '_, S, T> {
51 fn clone(&self) -> Self {
52 Self {
53 inner: self.inner.clone(),
54 env: self.env,
55 }
56 }
57}
58
59impl<S: Fcntl + Signals + Write, T: Input> Input for Reporter<'_, '_, S, T> {
60 #[allow(clippy::await_holding_refcell_ref)]
61 async fn next_line(&mut self, context: &Context) -> Result {
62 report(&mut self.env.borrow_mut()).await;
63 self.inner.next_line(context).await
64 }
65}
66
67async fn report<S: Fcntl + Signals + Write>(env: &mut Env<S>) {
68 if env.options.get(Interactive) == Off || env.options.get(Monitor) == Off {
69 return;
70 }
71
72 let mut accumulator = Accumulator::new();
73 accumulator.current_job_index = env.jobs.current_job();
74 accumulator.previous_job_index = env.jobs.previous_job();
75 env.jobs
76 .iter()
77 .filter(|(_, job)| job.state_changed)
78 .for_each(|(index, job)| accumulator.add(index, job, &env.system));
79
80 if env
81 .system
82 .write_all(Fd::STDERR, accumulator.print.as_bytes())
83 .await
84 .is_ok()
85 {
86 for index in accumulator.indices_reported {
87 env.jobs.get_mut(index).unwrap().state_reported();
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::super::Memory;
95 use super::*;
96 use crate::VirtualSystem;
97 use crate::job::{Job, Pid, ProcessState};
98 use crate::option::On;
99 use crate::system::r#virtual::SystemState;
100 use crate::tests::assert_stderr;
101 use futures_util::FutureExt as _;
102 use std::rc::Rc;
103
104 #[test]
105 fn reporter_reads_from_inner_input() {
106 let mut env = Env::new_virtual();
107 let ref_env = RefCell::new(&mut env);
108 let mut reporter = Reporter::new(Memory::new("echo hello"), &ref_env);
109 let result = reporter
110 .next_line(&Context::default())
111 .now_or_never()
112 .unwrap();
113 assert_eq!(result.unwrap(), "echo hello");
114 }
115
116 #[test]
117 fn reporter_shows_job_status_before_reading_input() {
118 let system = VirtualSystem::new();
119 let state = system.state.clone();
120 let mut env = Env::with_system(system);
121 env.jobs.add({
122 let mut job = Job::new(Pid(10));
123 job.state_changed = true;
124 job.name = "echo hello".to_string();
125 job
126 });
127 env.options.set(Interactive, On);
128 env.options.set(Monitor, On);
129
130 struct InputMock(Rc<RefCell<SystemState>>);
131 impl Input for InputMock {
132 async fn next_line(&mut self, _: &Context) -> Result {
133 assert_stderr(&self.0, |stderr| {
136 assert!(stderr.starts_with("[1]"), "stderr: {stderr:?}")
137 });
138
139 Ok("foo".to_string())
140 }
141 }
142
143 let ref_env = RefCell::new(&mut env);
144 let mut reporter = Reporter::new(InputMock(state), &ref_env);
145 let result = reporter
146 .next_line(&Context::default())
147 .now_or_never()
148 .unwrap();
149 assert_eq!(result.unwrap(), "foo"); }
151
152 #[test]
153 fn all_jobs_with_changed_status_are_reported() {
154 let system = VirtualSystem::new();
155 let state = system.state.clone();
156 let mut env = Env::with_system(system);
157 env.jobs.add({
158 let mut job = Job::new(Pid(10));
159 job.state_changed = true;
160 job.name = "echo hello".to_string();
161 job
162 });
163 env.jobs.add({
164 let mut job = Job::new(Pid(20));
165 job.state_changed = false;
166 job.name = "sleep 1".to_string();
167 job
168 });
169 env.jobs.add({
170 let mut job = Job::new(Pid(30));
171 job.state = ProcessState::exited(0);
172 job.state_changed = true;
173 job.name = "cat README".to_string();
174 job
175 });
176 env.options.set(Interactive, On);
177 env.options.set(Monitor, On);
178 let ref_env = RefCell::new(&mut env);
179 let memory = Memory::new("echo hello\n");
180 let mut reporter = Reporter::new(memory, &ref_env);
181
182 reporter
183 .next_line(&Context::default())
184 .now_or_never()
185 .unwrap()
186 .unwrap();
187 assert_stderr(&state, |stderr| {
188 let mut lines = stderr.lines();
189 let first = lines.next().unwrap();
190 assert!(first.starts_with("[1]"), "first: {first:?}");
191 assert!(first.contains("Running"), "first: {first:?}");
192 assert!(first.contains("echo hello"), "first: {first:?}");
193 let second = lines.next().unwrap();
194 assert!(second.starts_with("[3]"), "second: {second:?}");
195 assert!(second.contains("Done"), "second: {second:?}");
196 assert!(second.contains("cat README"), "second: {second:?}");
197 assert_eq!(lines.next(), None);
198 });
199 }
200
201 #[test]
202 fn reporter_clears_state_changed_flag() {
203 let mut env = Env::new_virtual();
204 let index = env.jobs.add({
205 let mut job = Job::new(Pid(10));
206 job.state_changed = true;
207 job.name = "echo hello".to_string();
208 job
209 });
210 env.options.set(Interactive, On);
211 env.options.set(Monitor, On);
212 let ref_env = RefCell::new(&mut env);
213 let memory = Memory::new("echo hello\n");
214 let mut reporter = Reporter::new(memory, &ref_env);
215
216 reporter
217 .next_line(&Context::default())
218 .now_or_never()
219 .unwrap()
220 .unwrap();
221 assert!(!env.jobs.get(index).unwrap().state_changed);
222 }
223
224 #[test]
225 fn no_report_if_not_interactive() {
226 let system = VirtualSystem::new();
227 let state = system.state.clone();
228 let mut env = Env::with_system(system);
229 env.jobs.add({
230 let mut job = Job::new(Pid(10));
231 job.state_changed = true;
232 job.name = "echo hello".to_string();
233 job
234 });
235 env.options.set(Monitor, On);
236 let ref_env = RefCell::new(&mut env);
237 let memory = Memory::new("echo hello\n");
238 let mut reporter = Reporter::new(memory, &ref_env);
239
240 reporter
241 .next_line(&Context::default())
242 .now_or_never()
243 .unwrap()
244 .unwrap();
245 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
246 }
247
248 #[test]
249 fn no_report_if_not_monitor() {
250 let system = VirtualSystem::new();
251 let state = system.state.clone();
252 let mut env = Env::with_system(system);
253 env.jobs.add({
254 let mut job = Job::new(Pid(10));
255 job.state_changed = true;
256 job.name = "echo hello".to_string();
257 job
258 });
259 env.options.set(Interactive, On);
260 let ref_env = RefCell::new(&mut env);
261 let memory = Memory::new("echo hello\n");
262 let mut reporter = Reporter::new(memory, &ref_env);
263
264 reporter
265 .next_line(&Context::default())
266 .now_or_never()
267 .unwrap()
268 .unwrap();
269 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
270 }
271}