diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..47704ee --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,68 @@ +name: Docs + +on: + push: + branches: [ "main" ] + +concurrency: + group: deploy + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Cache Dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-rust- + + - name: Setup pages + id: pages + uses: actions/configure-pages@v5 + + - name: Clean docs folder + run: cargo clean --doc + + - name: Build docs + run: cargo doc --no-deps + + - name: Add redirect + run: echo '' > target/doc/index.html + + - name: Remove lock file + run: rm target/doc/.lock + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: target/doc + + deploy: + runs-on: ubuntu-latest + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..db66f47 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,66 @@ +name: Rust + +on: + push: + branches: [ "main" ] + + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt, clippy + + - name: Cache Dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-rust- + + - name: Format + uses: actions-rust-lang/rustfmt@v1 + + - name: Lint + run: cargo clippy --workspace --all-targets --all-features -- -D warnings + env: + RUSTFLAGS: "-D warnings" + + - name: Install Tools + uses: taiki-e/install-action@v2 + with: + tool: cargo-llvm-cov, nextest + + - name: Test + run: cargo llvm-cov nextest --no-fail-fast --lcov --output-path lcov.info + + - name: Upload Coverage + if: matrix.os == 'ubuntu-latest' + uses: codecov/codecov-action@v3 + with: + files: lcov.info + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..07ef896 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.check.extraArgs": [ + "--all-features", + "--", + "-D warnings" + ], + "rust-analyzer.check.command": "clippy" +} diff --git a/Cargo.lock b/Cargo.lock index 6a77e5e..fe034aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,7 @@ dependencies = [ "num-traits", "serde", "serde_json", + "serde_test", ] [[package]] @@ -101,6 +102,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +dependencies = [ + "serde", +] + [[package]] name = "syn" version = "2.0.101" diff --git a/Cargo.toml b/Cargo.toml index 06e24c8..860df55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,4 @@ default-features = false [dev-dependencies] libc = "0.2.172" serde_json = "1.0.140" +serde_test = "1.0.177" diff --git a/README.md b/README.md index 436d37f..d201a13 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,11 @@ Unix exit code from a raw integer: ```rust use proc_result::unix::ExitCode; -fn main() { - let code = ExitCode::from_raw(1); - if code.is_success() { - println!("Command succeeded!"); - } else { - eprintln!("Command failed with exit code: {}", code); - } +let code = ExitCode::from_raw(1); +if code.is_success() { + println!("Command succeeded!"); +} else { + eprintln!("Command failed with exit code: {}", code); } ``` diff --git a/src/unix/exit_code.rs b/src/unix/exit_code.rs index e02c956..f967706 100644 --- a/src/unix/exit_code.rs +++ b/src/unix/exit_code.rs @@ -4,7 +4,11 @@ use crate::raw::RawExitCode; /// A Unix-like exit code. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(transparent) +)] pub struct ExitCode(u8); impl ExitCode { diff --git a/src/unix/signal.rs b/src/unix/signal.rs index 1142b0e..0cddd96 100644 --- a/src/unix/signal.rs +++ b/src/unix/signal.rs @@ -4,7 +4,11 @@ use core::fmt::Display; /// /// Represents a signal that can be sent to or received by processes on Unix-like systems. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(transparent) +)] pub struct Signal(u8); impl Signal { diff --git a/src/unix/wait_state.rs b/src/unix/wait_state.rs index 6113358..78a1734 100644 --- a/src/unix/wait_state.rs +++ b/src/unix/wait_state.rs @@ -23,14 +23,8 @@ pub enum WaitState { core_dump: bool, }, - /// Indicates that the process was stopped (paused) by a signal. - Stopped { - /// The signal that caused the process to stop. - signal: Signal, - }, - - /// Indicates that the process was continued after being stopped. - Continued, + /// Indicates a wait status code that is not recognized or supported. + Unsupported(i32), } impl WaitState { @@ -46,14 +40,8 @@ impl WaitState { signal: Signal::from_raw(Self::w_term_sig(status)), core_dump: Self::is_w_coredump(status), } - } else if Self::is_w_stopped(status) { - Self::Stopped { - signal: Signal::from_raw(Self::w_stop_sig(status)), - } - } else if Self::is_w_continued(status) { - Self::Continued } else { - unreachable!() + Self::Unsupported(status) } } @@ -72,8 +60,7 @@ impl WaitState { Self::Signaled { signal, core_dump } => { (signal.to_raw() as i32) | if *core_dump { 0x80 } else { 0 } } - Self::Stopped { signal } => 0x7F | ((signal.to_raw() as i32) << 8), - Self::Continued => 0x7F, + Self::Unsupported(code) => *code, } } @@ -96,14 +83,6 @@ impl WaitState { status & 0xFF } - /// A copy of the Unix `WIFCONTINUED(status)` macro. - #[allow(non_snake_case)] - #[inline] - #[must_use] - const fn WIFCONTINUED(status: i32) -> bool { - Self::_WSTATUS(status) == Self::_WSTOPPED && Self::WSTOPSIG(status) == 0x13 - } - /// A copy of the Unix `WIFSIGNALED(status)` macro. #[allow(non_snake_case)] #[inline] @@ -112,14 +91,6 @@ impl WaitState { Self::_WSTATUS(status) != Self::_WSTOPPED && Self::_WSTATUS(status) != 0 } - /// A copy of the Unix `WIFSTOPPED(status)` macro. - #[allow(non_snake_case)] - #[inline] - #[must_use] - const fn WIFSTOPPED(status: i32) -> bool { - Self::_WSTATUS(status) == Self::_WSTOPPED && Self::WSTOPSIG(status) != 0x13 - } - /// A copy of the Unix `WTERMSIG(status)` macro. #[allow(non_snake_case)] #[inline] @@ -194,14 +165,6 @@ impl WaitState { Self::WTERMSIG(status) as u8 } - /// Returns `true` if the status indicates that the process was stopped by a signal. - /// - /// Equivalent to the Unix `WIFSTOPPED(status)` macro. - #[must_use] - pub const fn is_w_stopped(status: i32) -> bool { - Self::WIFSTOPPED(status) - } - /// Returns the signal number that caused the process to stop. /// /// Equivalent to the Unix `WSTOPSIG(status)` macro. @@ -215,14 +178,6 @@ impl WaitState { Self::WSTOPSIG(status) as u8 } - /// Returns `true` if the status indicates that the process was continued. - /// - /// Equivalent to the Unix `WIFCONTINUED(status)` macro. - #[must_use] - pub const fn is_w_continued(status: i32) -> bool { - Self::WIFCONTINUED(status) - } - /// Returns `true` if the status indicates a core dump occurred. /// /// Equivalent to the Unix `WCOREDUMP(status)` macro. @@ -326,47 +281,13 @@ mod tests { }; assert_eq!(status.to_raw(), 0x0000_0081); } - - #[test] - fn test_from_raw_stopped() { - let status = WaitState::from_raw(0x0000_007F); - assert_eq!( - status, - WaitState::Stopped { - signal: Signal::from_raw(0), - } - ); - } - - #[test] - fn test_to_raw_stopped() { - let status = WaitState::Stopped { - signal: Signal::from_raw(0), - }; - assert_eq!(status.to_raw(), 0x0000_007F); - } - - #[test] - fn test_from_raw_continued() { - let status = WaitState::from_raw(0x0000_137F); - assert_eq!(status, WaitState::Continued); - } - - #[test] - fn test_to_raw_continued() { - let status = WaitState::Continued; - assert_eq!(status.to_raw(), 0x7F); - } } // Tests that compare the behavior of the `UnixWaitIf` struct with the libc macros. -#[cfg(test)] +#[cfg(all(test, unix))] mod libc_verification_tests { use super::*; - use libc::{ - WCOREDUMP, WEXITSTATUS, WIFCONTINUED, WIFEXITED, WIFSIGNALED, WIFSTOPPED, WSTOPSIG, - WTERMSIG, - }; + use libc::{WCOREDUMP, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WSTOPSIG, WTERMSIG}; #[test] fn test_wifexited_true() { @@ -410,36 +331,12 @@ mod libc_verification_tests { assert_eq!(WaitState::w_term_sig(0x0000_0001), 1); } - #[test] - fn test_wifstopped_true() { - assert!(WIFSTOPPED(0x0000_007F)); - assert!(WaitState::is_w_stopped(0x0000_007F)); - } - - #[test] - fn test_wifstopped_false() { - assert!(!WIFSTOPPED(0x0000_0000)); - assert!(!WaitState::is_w_stopped(0x0000_0000)); - } - #[test] fn test_wstopsig() { assert_eq!(WaitState::w_stop_sig(0x0000_007F), 0); assert_eq!(WSTOPSIG(0x0000_007F), 0); } - #[test] - fn test_wifcontinued_true() { - assert!(WIFCONTINUED(0x0000_137F)); - assert!(WaitState::is_w_continued(0x0000_137F)); - } - - #[test] - fn test_wifcontinued_false() { - assert!(!WIFCONTINUED(0x0000_0000)); - assert!(!WaitState::is_w_continued(0x0000_0000)); - } - #[test] fn test_wcoredump_true() { assert!(WCOREDUMP(0x0000_0081)); diff --git a/src/unix/wait_status.rs b/src/unix/wait_status.rs index ce02ded..d76ac60 100644 --- a/src/unix/wait_status.rs +++ b/src/unix/wait_status.rs @@ -1,5 +1,3 @@ -use std::os::unix::process::ExitStatusExt; - use super::{ExitCode, Signal, WaitState}; /// A Unix-like wait status. @@ -8,7 +6,11 @@ use super::{ExitCode, Signal, WaitState}; /// this struct encapsulates that information and can separate the exit code from the signal that /// caused the termination, or whether the process was stopped or continued. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(transparent) +)] pub struct WaitStatus(i32); impl WaitStatus { @@ -45,18 +47,6 @@ impl WaitStatus { matches!(self.state(), WaitState::Exited { .. }) } - /// Returns `true` if the process was stopped by a signal. - #[must_use] - pub const fn is_stopped(&self) -> bool { - matches!(self.state(), WaitState::Stopped { .. }) - } - - /// Returns `true` if the process was continued after being stopped. - #[must_use] - pub const fn is_continued(&self) -> bool { - matches!(self.state(), WaitState::Continued) - } - /// Returns `true` if the process was terminated by a signal. #[must_use] pub const fn is_signaled(&self) -> bool { @@ -76,9 +66,8 @@ impl WaitStatus { #[must_use] pub const fn signal(&self) -> Option { match self.state() { - WaitState::Stopped { signal, .. } | WaitState::Signaled { signal, .. } => Some(signal), - WaitState::Continued => Some(Signal::CONTINUE), - WaitState::Exited { exit_code: _ } => None, + WaitState::Signaled { signal, .. } => Some(signal), + _ => None, } } } @@ -89,6 +78,7 @@ impl From<&std::process::ExitStatus> for WaitStatus { if let Some(code) = status.code() { WaitStatus::from_raw(code) } else { + use std::os::unix::process::ExitStatusExt; WaitStatus::from_raw(status.into_raw()) } } @@ -97,6 +87,7 @@ impl From<&std::process::ExitStatus> for WaitStatus { #[cfg(all(unix, feature = "std"))] impl From<&WaitStatus> for std::process::ExitStatus { fn from(status: &WaitStatus) -> Self { + use std::os::unix::process::ExitStatusExt; std::process::ExitStatus::from_raw(status.to_raw()) } } diff --git a/src/windows.rs b/src/windows.rs index 5fe4024..1c7a6fe 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -8,7 +8,11 @@ use core::fmt::Display; /// A Windows-specific exit code. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(transparent) +)] pub struct ExitCode(u32); impl ExitCode { @@ -106,7 +110,13 @@ impl Display for ExitCode { #[cfg(all(windows, feature = "std"))] impl From<&std::process::ExitStatus> for ExitCode { fn from(status: &std::process::ExitStatus) -> ExitCode { - ExitCode::from_raw(status.code().expect("cannot fail on Windows")) + ExitCode::from_raw( + status + .code() + .expect("cannot fail on Windows") + .try_into() + .unwrap(), + ) } } @@ -146,27 +156,27 @@ mod tests { #[test] #[cfg(all(feature = "std", windows))] fn test_from_exit_status() { + use std::os::windows::process::ExitStatusExt; use std::process::ExitStatus; // Simulate a successful exit status let success_status = ExitStatus::from_raw(0); - let success_code: ExitCode = success_status.into(); + let success_code: ExitCode = (&success_status).into(); assert!(success_code.is_success()); assert_eq!(success_code.to_raw(), 0); // Simulate a failure exit status let failure_status = ExitStatus::from_raw(1); - let failure_code: ExitCode = failure_status.into(); + let failure_code: ExitCode = (&failure_status).into(); assert!(failure_code.is_failure()); assert_eq!(failure_code.to_raw(), 1); } } -#[cfg(all(test, feature = "serde"))] +#[cfg(all(test, windows, feature = "serde"))] mod serde_tests { use super::*; use serde_json; - use std::os::windows::process::ExitStatusExt; #[test] fn test_serde() {