• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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