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