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() {