1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #![allow(clippy::field_reassign_with_default)]
6
7 use std::io;
8 use std::io::Write;
9 use std::sync::Arc;
10
11 use base::syslog::test_only_ensure_inited;
12 use base::syslog::LogArgs;
13 use base::syslog::LogConfig;
14 use base::syslog::Priority;
15 use base::syslog::State;
16 use base::syslog::Syslogger;
17 use env_logger::fmt;
18 use log::Level;
19 use log::Log;
20 use log::Record;
21 use sync::Mutex;
22
23 #[derive(Clone)]
24 struct MockWrite {
25 buffer: Arc<Mutex<Vec<u8>>>,
26 }
27
28 impl MockWrite {
new() -> Self29 fn new() -> Self {
30 Self {
31 buffer: Arc::new(Mutex::new(vec![])),
32 }
33 }
34
into_inner(self) -> Vec<u8>35 fn into_inner(self) -> Vec<u8> {
36 Arc::try_unwrap(self.buffer).unwrap().into_inner()
37 }
38 }
39
40 impl Write for MockWrite {
write(&mut self, buf: &[u8]) -> io::Result<usize>41 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
42 self.buffer.lock().write(buf)
43 }
44
flush(&mut self) -> io::Result<()>45 fn flush(&mut self) -> io::Result<()> {
46 Ok(())
47 }
48 }
49
50 #[test]
syslog_log()51 fn syslog_log() {
52 let state = State::default();
53 state.log(
54 &log::RecordBuilder::new()
55 .level(Level::Error)
56 .file(Some(file!()))
57 .line(Some(line!()))
58 .args(format_args!("hello syslog"))
59 .build(),
60 );
61 }
62
63 #[test]
proc_name()64 fn proc_name() {
65 let state = State::new(LogConfig {
66 log_args: LogArgs {
67 proc_name: String::from("syslog-test"),
68 ..Default::default()
69 },
70 ..Default::default()
71 })
72 .unwrap();
73 state.log(
74 &log::RecordBuilder::new()
75 .level(Level::Error)
76 .file(Some(file!()))
77 .line(Some(line!()))
78 .args(format_args!("hello syslog"))
79 .build(),
80 );
81 }
82
83 #[test]
macros()84 fn macros() {
85 test_only_ensure_inited().unwrap();
86 log::error!("this is an error {}", 3);
87 log::warn!("this is a warning {}", "uh oh");
88 log::info!("this is info {}", true);
89 log::debug!("this is debug info {:?}", Some("helpful stuff"));
90 }
91
pipe_formatter(buf: &mut fmt::Formatter, record: &Record<'_>) -> io::Result<()>92 fn pipe_formatter(buf: &mut fmt::Formatter, record: &Record<'_>) -> io::Result<()> {
93 writeln!(buf, "{}", record.args())
94 }
95
96 #[test]
syslogger_char()97 fn syslogger_char() {
98 let output = MockWrite::new();
99 let mut cfg = LogConfig::default();
100 cfg.pipe_formatter = Some(Box::new(pipe_formatter));
101 cfg.pipe = Some(Box::new(output.clone()));
102 let state = Mutex::new(State::new(cfg).unwrap());
103
104 let mut syslogger = Syslogger::test_only_from_state(Level::Info, || state.lock());
105
106 let string = "chars";
107 for c in string.chars() {
108 syslogger.write_all(&[c as u8]).expect("error writing char");
109 }
110
111 syslogger
112 .write_all(b"\n")
113 .expect("error writing newline char");
114
115 std::mem::drop(syslogger);
116 std::mem::drop(state);
117 assert_eq!(
118 format!("{}\n", string),
119 String::from_utf8_lossy(&output.into_inner()[..])
120 );
121 }
122
123 #[test]
syslogger_line()124 fn syslogger_line() {
125 let output = MockWrite::new();
126 let mut cfg = LogConfig::default();
127 cfg.pipe_formatter = Some(Box::new(pipe_formatter));
128 cfg.pipe = Some(Box::new(output.clone()));
129 let state = Mutex::new(State::new(cfg).unwrap());
130
131 let mut syslogger = Syslogger::test_only_from_state(Level::Info, || state.lock());
132
133 let s = "Writing string to syslog\n";
134 syslogger
135 .write_all(s.as_bytes())
136 .expect("error writing string");
137
138 std::mem::drop(syslogger);
139 std::mem::drop(state);
140 assert_eq!(s, String::from_utf8_lossy(&output.into_inner()[..]));
141 }
142
143 #[test]
syslogger_partial()144 fn syslogger_partial() {
145 let output = MockWrite::new();
146 let state = Mutex::new(
147 State::new(LogConfig {
148 pipe: Some(Box::new(output.clone())),
149 ..Default::default()
150 })
151 .unwrap(),
152 );
153
154 let mut syslogger = Syslogger::test_only_from_state(Level::Info, || state.lock());
155
156 let s = "Writing partial string";
157 // Should not log because there is no newline character
158 syslogger
159 .write_all(s.as_bytes())
160 .expect("error writing string");
161
162 std::mem::drop(syslogger);
163 std::mem::drop(state);
164 assert_eq!(Vec::<u8>::new(), output.into_inner());
165 }
166
167 #[test]
log_priority_try_from_number()168 fn log_priority_try_from_number() {
169 assert_eq!("0".try_into(), Ok(Priority::Emergency));
170 assert!(Priority::try_from("100").is_err());
171 }
172
173 #[test]
log_priority_try_from_words()174 fn log_priority_try_from_words() {
175 assert_eq!("EMERGENCY".try_into(), Ok(Priority::Emergency));
176 assert!(Priority::try_from("_EMERGENCY").is_err());
177 }
178
179 #[test]
log_should_always_be_enabled_for_level_show_all()180 fn log_should_always_be_enabled_for_level_show_all() {
181 let state = State::new(LogConfig {
182 log_args: LogArgs {
183 filter: String::from("trace"),
184 ..Default::default()
185 },
186 ..Default::default()
187 })
188 .unwrap();
189
190 assert!(state.enabled(
191 log::RecordBuilder::new()
192 .level(Level::Debug)
193 .build()
194 .metadata(),
195 ));
196 }
197
198 #[test]
log_should_always_be_disabled_for_level_silent()199 fn log_should_always_be_disabled_for_level_silent() {
200 let state = State::new(LogConfig {
201 log_args: LogArgs {
202 filter: String::from("off"),
203 ..Default::default()
204 },
205 ..Default::default()
206 })
207 .unwrap();
208
209 assert!(!state.enabled(
210 log::RecordBuilder::new()
211 .level(Level::Debug)
212 .build()
213 .metadata(),
214 ));
215 }
216
217 #[test]
log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority()218 fn log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority() {
219 let state = State::new(LogConfig {
220 log_args: LogArgs {
221 filter: String::from("info"),
222 ..Default::default()
223 },
224 ..Default::default()
225 })
226 .unwrap();
227
228 assert!(state.enabled(
229 log::RecordBuilder::new()
230 .level(Level::Info)
231 .build()
232 .metadata(),
233 ));
234 assert!(state.enabled(
235 log::RecordBuilder::new()
236 .level(Level::Warn)
237 .build()
238 .metadata(),
239 ));
240 }
241
242 #[test]
log_should_be_disabled_if_filter_level_has_a_higher_priority()243 fn log_should_be_disabled_if_filter_level_has_a_higher_priority() {
244 let state = State::new(LogConfig {
245 log_args: LogArgs {
246 filter: String::from("info"),
247 ..Default::default()
248 },
249 ..Default::default()
250 })
251 .unwrap();
252
253 assert!(!state.enabled(
254 log::RecordBuilder::new()
255 .level(Level::Debug)
256 .build()
257 .metadata(),
258 ));
259 }
260
261 #[test]
path_overides_should_apply_to_logs()262 fn path_overides_should_apply_to_logs() {
263 let state = State::new(LogConfig {
264 log_args: LogArgs {
265 filter: String::from("info,test=debug"),
266 ..Default::default()
267 },
268 ..Default::default()
269 })
270 .unwrap();
271
272 assert!(!state.enabled(
273 log::RecordBuilder::new()
274 .level(Level::Debug)
275 .build()
276 .metadata(),
277 ));
278 assert!(state.enabled(
279 log::RecordBuilder::new()
280 .level(Level::Debug)
281 .target("test")
282 .build()
283 .metadata(),
284 ));
285 }
286
287 #[test]
longest_path_prefix_match_should_apply_if_multiple_filters_match()288 fn longest_path_prefix_match_should_apply_if_multiple_filters_match() {
289 let state = State::new(LogConfig {
290 log_args: LogArgs {
291 filter: String::from("info,test=debug,test::silence=off"),
292 ..Default::default()
293 },
294 ..Default::default()
295 })
296 .unwrap();
297
298 assert!(!state.enabled(
299 log::RecordBuilder::new()
300 .level(Level::Debug)
301 .build()
302 .metadata(),
303 ));
304
305 assert!(state.enabled(
306 log::RecordBuilder::new()
307 .level(Level::Debug)
308 .target("test")
309 .build()
310 .metadata(),
311 ));
312 assert!(!state.enabled(
313 log::RecordBuilder::new()
314 .level(Level::Error)
315 .target("test::silence")
316 .build()
317 .metadata(),
318 ));
319 }
320