// Copyright 2024 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. mod backend_tests; #[cfg(not(target_os = "macos"))] #[cfg(test)] fn flush_stdout() { // Safety: Test only. Calling into a libc function w/o any dependency on // data from the Rust side. unsafe { extern "C" { static stdout: *mut libc::FILE; } libc::fflush(stdout); } } #[cfg(target_os = "macos")] #[cfg(test)] fn flush_stdout() { // Safety: Test only. Calling into a libc function w/o any dependency on // data from the Rust side. unsafe { extern "C" { // MacOS uses a #define to declare stdout so we need to expand that // manually. static __stdoutp: *mut libc::FILE; } libc::fflush(__stdoutp); } } // Runs `action` while capturing stdout and returns the captured output. #[cfg(test)] fn run_with_capture(action: F) -> String { // Use statements here instead of at the module level to scope them to the // above #[cfg(test)] use nix::unistd::{dup, dup2, pipe}; use std::fs::File; use std::io::{stdout, Read}; use std::os::fd::AsRawFd; // Capture the output of printf by creating a pipe and replacing // `STDOUT_FILENO` with the write side of the pipe. This only works on // POSIX platforms (i.e. not Windows). Because the backend is written // against libc's printf, the behavior should be the same on POSIX and // Windows so there is little incremental benefit from testing on Windows // as well. // Grab a lock on stdout to prevent others (test framework and other tests) // from writing while we capture output. let stdout = stdout().lock(); let stdout_fd = stdout.as_raw_fd(); // Duplicate the current stdout so we can restore it after the test. Keep // it as an owned fd, let old_stdout = dup(stdout_fd).unwrap(); // Create a pipe that will let us read stdout. let (pipe_rx, pipe_tx) = pipe().unwrap(); // Since `printf` buffers output, we need to call into libc to flush the // buffers before swapping stdout. flush_stdout(); // Replace stdout with our pipe. dup2(pipe_tx.as_raw_fd(), stdout_fd).unwrap(); action(); // Flush buffers again before restoring stdout. flush_stdout(); // Restore old stdout. dup2(old_stdout, stdout_fd).unwrap(); // Drop the writer side of the pipe to close it so that read will see an // EOF. drop(pipe_tx); // Read the read side of the pipe into a `String`. let mut pipe_rx: File = pipe_rx.into(); let mut output = String::new(); pipe_rx.read_to_string(&mut output).unwrap(); output }