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 #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
15
16 mod backend_tests;
17
18 #[cfg(not(target_os = "macos"))]
19 #[cfg(test)]
flush_stdout()20 fn flush_stdout() {
21 // Safety: Test only. Calling into a libc function w/o any dependency on
22 // data from the Rust side.
23 unsafe {
24 extern "C" {
25 static stdout: *mut libc::FILE;
26 }
27 libc::fflush(stdout);
28 }
29 }
30
31 #[cfg(target_os = "macos")]
32 #[cfg(test)]
flush_stdout()33 fn flush_stdout() {
34 // Safety: Test only. Calling into a libc function w/o any dependency on
35 // data from the Rust side.
36 unsafe {
37 extern "C" {
38 // MacOS uses a #define to declare stdout so we need to expand that
39 // manually.
40 static __stdoutp: *mut libc::FILE;
41 }
42 libc::fflush(__stdoutp);
43 }
44 }
45
46 // Runs `action` while capturing stdout and returns the captured output.
47 #[cfg(test)]
run_with_capture<F: FnOnce()>(action: F) -> String48 fn run_with_capture<F: FnOnce()>(action: F) -> String {
49 // Use statements here instead of at the module level to scope them to the
50 // above #[cfg(test)]
51 use nix::unistd::{dup, dup2, pipe};
52 use std::fs::File;
53 use std::io::{stdout, Read};
54 use std::os::fd::AsRawFd;
55
56 // Capture the output of printf by creating a pipe and replacing
57 // `STDOUT_FILENO` with the write side of the pipe. This only works on
58 // POSIX platforms (i.e. not Windows). Because the backend is written
59 // against libc's printf, the behavior should be the same on POSIX and
60 // Windows so there is little incremental benefit from testing on Windows
61 // as well.
62
63 // Grab a lock on stdout to prevent others (test framework and other tests)
64 // from writing while we capture output.
65 let stdout = stdout().lock();
66
67 let stdout_fd = stdout.as_raw_fd();
68
69 // Duplicate the current stdout so we can restore it after the test. Keep
70 // it as an owned fd,
71 let old_stdout = dup(stdout_fd).unwrap();
72
73 // Create a pipe that will let us read stdout.
74 let (pipe_rx, pipe_tx) = pipe().unwrap();
75
76 // Since `printf` buffers output, we need to call into libc to flush the
77 // buffers before swapping stdout.
78 flush_stdout();
79
80 // Replace stdout with our pipe.
81 dup2(pipe_tx.as_raw_fd(), stdout_fd).unwrap();
82
83 action();
84
85 // Flush buffers again before restoring stdout.
86 flush_stdout();
87
88 // Restore old stdout.
89 dup2(old_stdout, stdout_fd).unwrap();
90
91 // Drop the writer side of the pipe to close it so that read will see an
92 // EOF.
93 drop(pipe_tx);
94
95 // Read the read side of the pipe into a `String`.
96 let mut pipe_rx: File = pipe_rx.into();
97 let mut output = String::new();
98 pipe_rx.read_to_string(&mut output).unwrap();
99
100 output
101 }
102