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