xref: /aosp_15_r20/external/pigweed/pw_log/rust/printf_backend_test.rs (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 mod backend_tests;
16 
17 #[cfg(not(target_os = "macos"))]
18 #[cfg(test)]
flush_stdout()19 fn flush_stdout() {
20     // Safety: Test only.  Calling into a libc function w/o any dependency on
21     // data from the Rust side.
22     unsafe {
23         extern "C" {
24             static stdout: *mut libc::FILE;
25         }
26         libc::fflush(stdout);
27     }
28 }
29 
30 #[cfg(target_os = "macos")]
31 #[cfg(test)]
flush_stdout()32 fn flush_stdout() {
33     // Safety: Test only.  Calling into a libc function w/o any dependency on
34     // data from the Rust side.
35     unsafe {
36         extern "C" {
37             // MacOS uses a #define to declare stdout so we need to expand that
38             // manually.
39             static __stdoutp: *mut libc::FILE;
40         }
41         libc::fflush(__stdoutp);
42     }
43 }
44 
45 // Runs `action` while capturing stdout and returns the captured output.
46 #[cfg(test)]
run_with_capture<F: FnOnce()>(action: F) -> String47 fn run_with_capture<F: FnOnce()>(action: F) -> String {
48     // Use statements here instead of at the module level to scope them to the
49     // above #[cfg(test)]
50     use nix::unistd::{dup, dup2, pipe};
51     use std::fs::File;
52     use std::io::{stdout, Read};
53     use std::os::fd::AsRawFd;
54 
55     // Capture the output of printf by creating a pipe and replacing
56     // `STDOUT_FILENO` with the write side of the pipe.  This only works on
57     // POSIX platforms (i.e. not Windows).  Because the backend is written
58     // against libc's printf, the behavior should be the same on POSIX and
59     // Windows so there is little incremental benefit from testing on Windows
60     // as well.
61 
62     // Grab a lock on stdout to prevent others (test framework and other tests)
63     // from writing while we capture output.
64     let stdout = stdout().lock();
65 
66     let stdout_fd = stdout.as_raw_fd();
67 
68     // Duplicate the current stdout so we can restore it after the test.  Keep
69     // it as an owned fd,
70     let old_stdout = dup(stdout_fd).unwrap();
71 
72     // Create a pipe that will let us read stdout.
73     let (pipe_rx, pipe_tx) = pipe().unwrap();
74 
75     // Since `printf` buffers output, we need to call into libc to flush the
76     // buffers before swapping stdout.
77     flush_stdout();
78 
79     // Replace stdout with our pipe.
80     dup2(pipe_tx.as_raw_fd(), stdout_fd).unwrap();
81 
82     action();
83 
84     // Flush buffers again before restoring stdout.
85     flush_stdout();
86 
87     // Restore old stdout.
88     dup2(old_stdout, stdout_fd).unwrap();
89 
90     // Drop the writer side of the pipe to close it so that read will see an
91     // EOF.
92     drop(pipe_tx);
93 
94     // Read the read side of the pipe into a `String`.
95     let mut pipe_rx: File = pipe_rx.into();
96     let mut output = String::new();
97     pipe_rx.read_to_string(&mut output).unwrap();
98 
99     output
100 }
101