1 // Copyright 2016 The android_logger Developers
2 //
3 // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 // http://opensource.org/licenses/MIT>, at your option. This file may not be
6 // copied, modified, or distributed except according to those terms.
7 
8 //! A logger which writes to android output.
9 //!
10 //! ## Example
11 //!
12 //! ```
13 //! #[macro_use] extern crate log;
14 //! extern crate android_logger;
15 //!
16 //! use log::LevelFilter;
17 //! use android_logger::Config;
18 //!
19 //! /// Android code may not have obvious "main", this is just an example.
20 //! fn main() {
21 //!     android_logger::init_once(
22 //!         Config::default().with_max_level(LevelFilter::Trace),
23 //!     );
24 //!
25 //!     debug!("this is a debug {}", "message");
26 //!     error!("this is printed by default");
27 //! }
28 //! ```
29 //!
30 //! ## Example with module path filter
31 //!
32 //! It is possible to limit log messages to output from a specific crate,
33 //! and override the logcat tag name (by default, the crate name is used):
34 //!
35 //! ```
36 //! #[macro_use] extern crate log;
37 //! extern crate android_logger;
38 //!
39 //! use log::LevelFilter;
40 //! use android_logger::{Config,FilterBuilder};
41 //!
42 //! fn main() {
43 //!     android_logger::init_once(
44 //!         Config::default()
45 //!             .with_max_level(LevelFilter::Trace)
46 //!             .with_tag("mytag")
47 //!             .with_filter(FilterBuilder::new().parse("debug,hello::crate=trace").build()),
48 //!     );
49 //!
50 //!     // ..
51 //! }
52 //! ```
53 //!
54 //! ## Example with a custom log formatter
55 //!
56 //! ```
57 //! use android_logger::Config;
58 //!
59 //! android_logger::init_once(
60 //!     Config::default()
61 //!         .with_max_level(log::LevelFilter::Trace)
62 //!         .format(|f, record| write!(f, "my_app: {}", record.args()))
63 //! )
64 //! ```
65 
66 #[cfg(target_os = "android")]
67 extern crate android_log_sys as log_ffi;
68 extern crate once_cell;
69 use once_cell::sync::OnceCell;
70 #[cfg(default_log_impl)]
71 use crate as log;
72 #[cfg(not(default_log_impl))]
73 #[macro_use]
74 extern crate log;
75 
76 #[cfg(not(default_log_impl))]
77 extern crate env_logger;
78 
79 use self::log::{Level, LevelFilter, Log, Metadata, Record};
80 #[cfg(target_os = "android")]
81 use self::log_ffi::LogPriority;
82 use std::ffi::{CStr, CString};
83 use std::fmt;
84 use std::mem::{self, MaybeUninit};
85 use std::ptr;
86 
87 #[cfg(default_log_impl)]
88 pub mod env_logger {
89     pub mod filter {
90         pub struct Filter;
91         impl Filter {
matches(&self, _: &crate::Record) -> bool92             pub fn matches(&self, _: &crate::Record) -> bool { true }
93         }
94     }
95 }
96 #[cfg(not(default_log_impl))]
97 pub use env_logger::filter::{Builder as FilterBuilder, Filter};
98 #[cfg(not(default_log_impl))]
99 pub use env_logger::fmt::Formatter;
100 
101 pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
102 
103 /// Possible identifiers of a specific buffer of Android logging system for
104 /// logging a message.
105 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
106 pub enum LogId {
107     /// Main log buffer.
108     ///
109     /// This is the only log buffer available to apps.
110     Main,
111 
112     /// Radio log buffer.
113     Radio,
114 
115     /// Event log buffer.
116     Events,
117 
118     /// System log buffer.
119     System,
120 
121     /// Crash log buffer.
122     Crash,
123 
124     /// Kernel log buffer.
125     Kernel,
126 
127     /// Security log buffer.
128     Security,
129 
130     /// Statistics log buffer.
131     Stats,
132 }
133 
134 #[cfg(target_os = "android")]
135 impl LogId {
to_native(log_id: Option<Self>) -> Option<log_ffi::log_id_t>136     const fn to_native(log_id: Option<Self>) -> Option<log_ffi::log_id_t> {
137         match log_id {
138             Some(Self::Main) => Some(log_ffi::log_id_t::MAIN),
139             Some(Self::Radio) => Some(log_ffi::log_id_t::RADIO),
140             Some(Self::Events) => Some(log_ffi::log_id_t::EVENTS),
141             Some(Self::System) => Some(log_ffi::log_id_t::SYSTEM),
142             Some(Self::Crash) => Some(log_ffi::log_id_t::CRASH),
143             Some(Self::Kernel) => Some(log_ffi::log_id_t::KERNEL),
144             Some(Self::Security) => Some(log_ffi::log_id_t::SECURITY),
145             Some(Self::Stats) => Some(log_ffi::log_id_t::STATS),
146             None => None,
147         }
148     }
149 }
150 
151 /// Outputs log to Android system.
152 #[cfg(target_os = "android")]
android_log( buf_id: Option<log_ffi::log_id_t>, prio: log_ffi::LogPriority, tag: &CStr, msg: &CStr, )153 fn android_log(
154     buf_id: Option<log_ffi::log_id_t>,
155     prio: log_ffi::LogPriority,
156     tag: &CStr,
157     msg: &CStr,
158 ) {
159     if let Some(buf_id) = buf_id {
160         unsafe {
161             log_ffi::__android_log_buf_write(
162                 buf_id as log_ffi::c_int,
163                 prio as log_ffi::c_int,
164                 tag.as_ptr() as *const log_ffi::c_char,
165                 msg.as_ptr() as *const log_ffi::c_char,
166             );
167         };
168     } else {
169         unsafe {
170             log_ffi::__android_log_write(
171                 prio as log_ffi::c_int,
172                 tag.as_ptr() as *const log_ffi::c_char,
173                 msg.as_ptr() as *const log_ffi::c_char,
174             );
175         };
176     }
177 }
178 
179 /// Dummy output placeholder for tests.
180 #[cfg(not(target_os = "android"))]
android_log(_buf_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr)181 fn android_log(_buf_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr) {}
182 
183 /// Underlying android logger backend
184 pub struct AndroidLogger {
185     config: OnceCell<Config>,
186 }
187 
188 impl AndroidLogger {
189     /// Create new logger instance from config
new(config: Config) -> AndroidLogger190     pub fn new(config: Config) -> AndroidLogger {
191         AndroidLogger {
192             config: OnceCell::from(config),
193         }
194     }
195 
config(&self) -> &Config196     fn config(&self) -> &Config {
197         self.config.get_or_init(Config::default)
198     }
199 }
200 
201 static ANDROID_LOGGER: OnceCell<AndroidLogger> = OnceCell::new();
202 
203 const LOGGING_TAG_MAX_LEN: usize = 23;
204 const LOGGING_MSG_MAX_LEN: usize = 4000;
205 
206 impl Default for AndroidLogger {
207     /// Create a new logger with default config
default() -> AndroidLogger208     fn default() -> AndroidLogger {
209         AndroidLogger {
210             config: OnceCell::from(Config::default()),
211         }
212     }
213 }
214 
215 impl Log for AndroidLogger {
enabled(&self, metadata: &Metadata) -> bool216     fn enabled(&self, metadata: &Metadata) -> bool {
217         let config = self.config();
218         // todo: consider __android_log_is_loggable.
219         metadata.level() <= config.log_level.unwrap_or_else(log::max_level)
220     }
221 
log(&self, record: &Record)222     fn log(&self, record: &Record) {
223         let config = self.config();
224 
225         if !self.enabled(record.metadata()) {
226             return;
227         }
228 
229         // this also checks the level, but only if a filter was
230         // installed.
231         if !config.filter_matches(record) {
232             return;
233         }
234 
235         // tag must not exceed LOGGING_TAG_MAX_LEN
236         let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
237 
238         let module_path = record.module_path().unwrap_or_default().to_owned();
239 
240         // If no tag was specified, use module name
241         let custom_tag = &config.tag;
242         let tag = custom_tag
243             .as_ref()
244             .map(|s| s.as_bytes())
245             .unwrap_or_else(|| module_path.as_bytes());
246 
247         // truncate the tag here to fit into LOGGING_TAG_MAX_LEN
248         self.fill_tag_bytes(&mut tag_bytes, tag);
249         // use stack array as C string
250         let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
251 
252         // message must not exceed LOGGING_MSG_MAX_LEN
253         // therefore split log message into multiple log calls
254         let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
255 
256         // If a custom tag is used, add the module path to the message.
257         // Use PlatformLogWriter to output chunks if they exceed max size.
258         let _ = match (custom_tag, &config.custom_format) {
259             (_, Some(format)) => format(&mut writer, record),
260             (Some(_), _) => fmt::write(
261                 &mut writer,
262                 format_args!("{}: {}", module_path, *record.args()),
263             ),
264             _ => fmt::write(&mut writer, *record.args()),
265         };
266 
267         // output the remaining message (this would usually be the most common case)
268         writer.flush();
269     }
270 
flush(&self)271     fn flush(&self) {}
272 }
273 
274 impl AndroidLogger {
fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8])275     fn fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8]) {
276         if tag.len() > LOGGING_TAG_MAX_LEN {
277             for (input, output) in tag
278                 .iter()
279                 .take(LOGGING_TAG_MAX_LEN - 2)
280                 .chain(b"..\0".iter())
281                 .zip(array.iter_mut())
282             {
283                 output.write(*input);
284             }
285         } else {
286             for (input, output) in tag.iter().chain(b"\0".iter()).zip(array.iter_mut()) {
287                 output.write(*input);
288             }
289         }
290     }
291 }
292 
293 /// Filter for android logger.
294 #[derive(Default)]
295 pub struct Config {
296     log_level: Option<LevelFilter>,
297     buf_id: Option<LogId>,
298     filter: Option<env_logger::filter::Filter>,
299     tag: Option<CString>,
300     custom_format: Option<FormatFn>,
301 }
302 
303 impl Config {
304     /// Changes the maximum log level.
305     ///
306     /// Note, that `Trace` is the maximum level, because it provides the
307     /// maximum amount of detail in the emitted logs.
308     ///
309     /// If `Off` level is provided, then nothing is logged at all.
310     ///
311     /// [`log::max_level()`] is considered as the default level.
with_max_level(mut self, level: LevelFilter) -> Self312     pub fn with_max_level(mut self, level: LevelFilter) -> Self {
313         self.log_level = Some(level);
314         self
315     }
316 
317     /// Changes the Android logging system buffer to be used.
318     ///
319     /// By default, logs are sent to the [`Main`] log. Other logging buffers may
320     /// only be accessible to certain processes.
321     ///
322     /// [`Main`]: LogId::Main
with_log_buffer(mut self, buf_id: LogId) -> Self323     pub fn with_log_buffer(mut self, buf_id: LogId) -> Self {
324         self.buf_id = Some(buf_id);
325         self
326     }
327 
filter_matches(&self, record: &Record) -> bool328     fn filter_matches(&self, record: &Record) -> bool {
329         if let Some(ref filter) = self.filter {
330             filter.matches(record)
331         } else {
332             true
333         }
334     }
335 
with_filter(mut self, filter: env_logger::filter::Filter) -> Self336     pub fn with_filter(mut self, filter: env_logger::filter::Filter) -> Self {
337         self.filter = Some(filter);
338         self
339     }
340 
with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self341     pub fn with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
342         self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
343         self
344     }
345 
346     /// Sets the format function for formatting the log output.
347     /// ```
348     /// # use android_logger::Config;
349     /// android_logger::init_once(
350     ///     Config::default()
351     ///         .with_max_level(log::LevelFilter::Trace)
352     ///         .format(|f, record| write!(f, "my_app: {}", record.args()))
353     /// )
354     /// ```
format<F>(mut self, format: F) -> Self where F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,355     pub fn format<F>(mut self, format: F) -> Self
356     where
357         F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,
358     {
359         self.custom_format = Some(Box::new(format));
360         self
361     }
362 }
363 
364 pub struct PlatformLogWriter<'a> {
365     #[cfg(target_os = "android")]
366     priority: LogPriority,
367     #[cfg(not(target_os = "android"))]
368     priority: Level,
369     #[cfg(target_os = "android")]
370     buf_id: Option<log_ffi::log_id_t>,
371     #[cfg(not(target_os = "android"))]
372     buf_id: Option<LogId>,
373     len: usize,
374     last_newline_index: usize,
375     tag: &'a CStr,
376     buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
377 }
378 
379 impl<'a> PlatformLogWriter<'a> {
380     #[cfg(target_os = "android")]
new_with_priority( buf_id: Option<LogId>, priority: log_ffi::LogPriority, tag: &CStr, ) -> PlatformLogWriter<'_>381     pub fn new_with_priority(
382         buf_id: Option<LogId>,
383         priority: log_ffi::LogPriority,
384         tag: &CStr,
385     ) -> PlatformLogWriter<'_> {
386         #[allow(deprecated)] // created an issue #35 for this
387         PlatformLogWriter {
388             priority,
389             buf_id: LogId::to_native(buf_id),
390             len: 0,
391             last_newline_index: 0,
392             tag,
393             buffer: uninit_array(),
394         }
395     }
396 
397     #[cfg(target_os = "android")]
new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_>398     pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
399         PlatformLogWriter::new_with_priority(
400             buf_id,
401             match level {
402                 Level::Warn => LogPriority::WARN,
403                 Level::Info => LogPriority::INFO,
404                 Level::Debug => LogPriority::DEBUG,
405                 Level::Error => LogPriority::ERROR,
406                 Level::Trace => LogPriority::VERBOSE,
407             },
408             tag,
409         )
410     }
411 
412     #[cfg(not(target_os = "android"))]
new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_>413     pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
414         #[allow(deprecated)] // created an issue #35 for this
415         PlatformLogWriter {
416             priority: level,
417             buf_id,
418             len: 0,
419             last_newline_index: 0,
420             tag,
421             buffer: uninit_array(),
422         }
423     }
424 
425     /// Flush some bytes to android logger.
426     ///
427     /// If there is a newline, flush up to it.
428     /// If ther was no newline, flush all.
429     ///
430     /// Not guaranteed to flush everything.
temporal_flush(&mut self)431     fn temporal_flush(&mut self) {
432         let total_len = self.len;
433 
434         if total_len == 0 {
435             return;
436         }
437 
438         if self.last_newline_index > 0 {
439             let copy_from_index = self.last_newline_index;
440             let remaining_chunk_len = total_len - copy_from_index;
441 
442             self.output_specified_len(copy_from_index);
443             self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
444             self.len = remaining_chunk_len;
445         } else {
446             self.output_specified_len(total_len);
447             self.len = 0;
448         }
449         self.last_newline_index = 0;
450     }
451 
452     /// Flush everything remaining to android logger.
flush(&mut self)453     pub fn flush(&mut self) {
454         let total_len = self.len;
455 
456         if total_len == 0 {
457             return;
458         }
459 
460         self.output_specified_len(total_len);
461         self.len = 0;
462         self.last_newline_index = 0;
463     }
464 
465     /// Output buffer up until the \0 which will be placed at `len` position.
output_specified_len(&mut self, len: usize)466     fn output_specified_len(&mut self, len: usize) {
467         let mut last_byte = MaybeUninit::new(b'\0');
468 
469         mem::swap(&mut last_byte, unsafe {
470             self.buffer.get_unchecked_mut(len)
471         });
472 
473         let msg: &CStr = unsafe { CStr::from_ptr(self.buffer.as_ptr().cast()) };
474         android_log(self.buf_id, self.priority, self.tag, msg);
475 
476         unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
477     }
478 
479     /// Copy `len` bytes from `index` position to starting position.
copy_bytes_to_start(&mut self, index: usize, len: usize)480     fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
481         let dst = self.buffer.as_mut_ptr();
482         let src = unsafe { self.buffer.as_ptr().add(index) };
483         unsafe { ptr::copy(src, dst, len) };
484     }
485 }
486 
487 impl<'a> fmt::Write for PlatformLogWriter<'a> {
write_str(&mut self, s: &str) -> fmt::Result488     fn write_str(&mut self, s: &str) -> fmt::Result {
489         let mut incomming_bytes = s.as_bytes();
490 
491         while !incomming_bytes.is_empty() {
492             let len = self.len;
493 
494             // write everything possible to buffer and mark last \n
495             let new_len = len + incomming_bytes.len();
496             let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
497                 .iter_mut()
498                 .zip(incomming_bytes)
499                 .enumerate()
500                 .fold(None, |acc, (i, (output, input))| {
501                     output.write(*input);
502                     if *input == b'\n' {
503                         Some(i)
504                     } else {
505                         acc
506                     }
507                 });
508 
509             // update last \n index
510             if let Some(newline) = last_newline {
511                 self.last_newline_index = len + newline;
512             }
513 
514             // calculate how many bytes were written
515             let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
516                 // if the len was not exceeded
517                 self.len = new_len;
518                 new_len - len // written len
519             } else {
520                 // if new length was exceeded
521                 self.len = LOGGING_MSG_MAX_LEN;
522                 self.temporal_flush();
523 
524                 LOGGING_MSG_MAX_LEN - len // written len
525             };
526 
527             incomming_bytes = &incomming_bytes[written_len..];
528         }
529 
530         Ok(())
531     }
532 }
533 
534 /// Send a log record to Android logging backend.
535 ///
536 /// This action does not require initialization. However, without initialization it
537 /// will use the default filter, which allows all logs.
log(record: &Record)538 pub fn log(record: &Record) {
539     ANDROID_LOGGER
540         .get_or_init(AndroidLogger::default)
541         .log(record)
542 }
543 
544 /// Initializes the global logger with an android logger.
545 ///
546 /// This can be called many times, but will only initialize logging once,
547 /// and will not replace any other previously initialized logger.
548 ///
549 /// It is ok to call this at the activity creation, and it will be
550 /// repeatedly called on every lifecycle restart (i.e. screen rotation).
init_once(config: Config)551 pub fn init_once(config: Config) {
552     let log_level = config.log_level;
553     let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
554 
555     if let Err(err) = log::set_logger(logger) {
556         debug!("android_logger: log::set_logger failed: {}", err);
557     } else if let Some(level) = log_level {
558         log::set_max_level(level);
559     }
560     // On Android, log crate is patched to default to LevelFilter::Trace rather than Off. Preserve
561     // the existing "android_logger default level is Off" behavior by explicitly setting the level.
562     #[cfg(target_os = "android")]
563     if log_level.is_none() {
564         log::set_max_level(LevelFilter::Off);
565     }
566 }
567 
568 // FIXME: When `maybe_uninit_uninit_array ` is stabilized, use it instead of this helper
uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N]569 fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
570     // SAFETY: Array contains MaybeUninit, which is fine to be uninit
571     unsafe { MaybeUninit::uninit().assume_init() }
572 }
573 
574 #[cfg(test)]
575 mod tests {
576     use super::*;
577     use std::fmt::Write;
578     use std::sync::atomic::{AtomicBool, Ordering};
579 
580     #[test]
check_config_values()581     fn check_config_values() {
582         // Filter is checked in config_filter_match below.
583         let config = Config::default()
584             .with_max_level(LevelFilter::Trace)
585             .with_log_buffer(LogId::System)
586             .with_tag("my_app");
587 
588         assert_eq!(config.log_level, Some(LevelFilter::Trace));
589         assert_eq!(config.buf_id, Some(LogId::System));
590         assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
591     }
592 
593     #[test]
log_calls_formatter()594     fn log_calls_formatter() {
595         static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
596         let config = Config::default()
597             .with_max_level(LevelFilter::Info)
598             .format(|_, _| {
599                 FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
600                 Ok(())
601             });
602         let logger = AndroidLogger::new(config);
603 
604         logger.log(&Record::builder().level(Level::Info).build());
605 
606         assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
607     }
608 
609     #[test]
logger_enabled_threshold()610     fn logger_enabled_threshold() {
611         let logger = AndroidLogger::new(Config::default().with_max_level(LevelFilter::Info));
612 
613         assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Warn).build()));
614         assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Info).build()));
615         assert!(!logger.enabled(&log::MetadataBuilder::new().level(Level::Debug).build()));
616     }
617 
618     // Test whether the filter gets called correctly. Not meant to be exhaustive for all filter
619     // options, as these are handled directly by the filter itself.
620     #[cfg(not(default_log_impl))]
621     #[test]
config_filter_match()622     fn config_filter_match() {
623         let info_record = Record::builder().level(Level::Info).build();
624         let debug_record = Record::builder().level(Level::Debug).build();
625 
626         let info_all_filter = env_logger::filter::Builder::new().parse("info").build();
627         let info_all_config = Config::default().with_filter(info_all_filter);
628 
629         assert!(info_all_config.filter_matches(&info_record));
630         assert!(!info_all_config.filter_matches(&debug_record));
631     }
632 
633     #[test]
fill_tag_bytes_truncates_long_tag()634     fn fill_tag_bytes_truncates_long_tag() {
635         let logger = AndroidLogger::new(Config::default());
636         let too_long_tag: [u8; LOGGING_TAG_MAX_LEN + 20] = [b'a'; LOGGING_TAG_MAX_LEN + 20];
637 
638         let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
639         logger.fill_tag_bytes(&mut result, &too_long_tag);
640 
641         let mut expected_result = [b'a'; LOGGING_TAG_MAX_LEN - 2].to_vec();
642         expected_result.extend("..\0".as_bytes());
643         assert_eq!(unsafe { assume_init_slice(&result) }, expected_result);
644     }
645 
646     #[test]
fill_tag_bytes_keeps_short_tag()647     fn fill_tag_bytes_keeps_short_tag() {
648         let logger = AndroidLogger::new(Config::default());
649         let short_tag: [u8; 3] = [b'a'; 3];
650 
651         let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
652         logger.fill_tag_bytes(&mut result, &short_tag);
653 
654         let mut expected_result = short_tag.to_vec();
655         expected_result.push(0);
656         assert_eq!(unsafe { assume_init_slice(&result[..4]) }, expected_result);
657     }
658 
659     #[test]
platform_log_writer_init_values()660     fn platform_log_writer_init_values() {
661         let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
662 
663         let writer = PlatformLogWriter::new(None, Level::Warn, tag);
664 
665         assert_eq!(writer.tag, tag);
666         // Android uses LogPriority instead, which doesn't implement equality checks
667         #[cfg(not(target_os = "android"))]
668         assert_eq!(writer.priority, Level::Warn);
669     }
670 
671     #[test]
temporal_flush()672     fn temporal_flush() {
673         let mut writer = get_tag_writer();
674 
675         writer
676             .write_str("12\n\n567\n90")
677             .expect("Unable to write to PlatformLogWriter");
678 
679         assert_eq!(writer.len, 10);
680         writer.temporal_flush();
681         // Should have flushed up until the last newline.
682         assert_eq!(writer.len, 3);
683         assert_eq!(writer.last_newline_index, 0);
684         assert_eq!(
685             unsafe { assume_init_slice(&writer.buffer[..writer.len]) },
686             "\n90".as_bytes()
687         );
688 
689         writer.temporal_flush();
690         // Should have flushed all remaining bytes.
691         assert_eq!(writer.len, 0);
692         assert_eq!(writer.last_newline_index, 0);
693     }
694 
695     #[test]
flush()696     fn flush() {
697         let mut writer = get_tag_writer();
698         writer
699             .write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
700             .expect("Unable to write to PlatformLogWriter");
701 
702         writer.flush();
703 
704         assert_eq!(writer.last_newline_index, 0);
705         assert_eq!(writer.len, 0);
706     }
707 
708     #[test]
last_newline_index()709     fn last_newline_index() {
710         let mut writer = get_tag_writer();
711 
712         writer
713             .write_str("12\n\n567\n90")
714             .expect("Unable to write to PlatformLogWriter");
715 
716         assert_eq!(writer.last_newline_index, 7);
717     }
718 
719     #[test]
output_specified_len_leaves_buffer_unchanged()720     fn output_specified_len_leaves_buffer_unchanged() {
721         let mut writer = get_tag_writer();
722         let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
723         writer
724             .write_str(log_string)
725             .expect("Unable to write to PlatformLogWriter");
726 
727         writer.output_specified_len(5);
728 
729         assert_eq!(
730             unsafe { assume_init_slice(&writer.buffer[..log_string.len()]) },
731             log_string.as_bytes()
732         );
733     }
734 
735     #[test]
copy_bytes_to_start()736     fn copy_bytes_to_start() {
737         let mut writer = get_tag_writer();
738         writer
739             .write_str("0123456789")
740             .expect("Unable to write to PlatformLogWriter");
741 
742         writer.copy_bytes_to_start(3, 2);
743 
744         assert_eq!(
745             unsafe { assume_init_slice(&writer.buffer[..10]) },
746             "3423456789".as_bytes()
747         );
748     }
749 
750     #[test]
copy_bytes_to_start_nop()751     fn copy_bytes_to_start_nop() {
752         let test_string = "Test_string_with\n\n\n\nnewlines\n";
753         let mut writer = get_tag_writer();
754         writer
755             .write_str(test_string)
756             .expect("Unable to write to PlatformLogWriter");
757 
758         writer.copy_bytes_to_start(0, 20);
759         writer.copy_bytes_to_start(10, 0);
760 
761         assert_eq!(
762             unsafe { assume_init_slice(&writer.buffer[..test_string.len()]) },
763             test_string.as_bytes()
764         );
765     }
766 
get_tag_writer() -> PlatformLogWriter<'static>767     fn get_tag_writer() -> PlatformLogWriter<'static> {
768         PlatformLogWriter::new(
769             None,
770             Level::Warn,
771             CStr::from_bytes_with_nul(b"tag\0").unwrap(),
772         )
773     }
774 
assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T]775     unsafe fn assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T] {
776         &*(slice as *const [MaybeUninit<T>] as *const [T])
777     }
778 }
779