Adapt supervisor to process-wrap (#815)

This commit is contained in:
Félix Saparelli 2024-04-20 16:58:17 +12:00 committed by GitHub
parent a6e0b3f70a
commit 75b2c4b4ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 367 additions and 242 deletions

205
Cargo.lock generated
View File

@ -564,6 +564,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.31" version = "0.4.31"
@ -673,30 +679,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "command-group"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916"
dependencies = [
"async-trait",
"nix 0.26.4",
"tokio",
"winapi",
]
[[package]]
name = "command-group"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409"
dependencies = [
"async-trait",
"nix 0.27.1",
"tokio",
"winapi",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.4.0" version = "2.4.0"
@ -1758,7 +1740,7 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http",
"indexmap 2.1.0", "indexmap 2.2.6",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@ -1874,7 +1856,7 @@ dependencies = [
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"socket2 0.4.10", "socket2 0.5.5",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@ -1904,7 +1886,7 @@ dependencies = [
"iana-time-zone-haiku", "iana-time-zone-haiku",
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
"windows-core 0.52.0", "windows-core 0.53.0",
] ]
[[package]] [[package]]
@ -1972,9 +1954,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.1.0" version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.3", "hashbrown 0.14.3",
@ -2098,9 +2080,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.151" version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]] [[package]]
name = "libm" name = "libm"
@ -2351,6 +2333,18 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -2758,6 +2752,20 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "process-wrap"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df785e1a8a4f462ab97abb49e1718d8ee173f4fc37c29dde7d0487b6538c3a82"
dependencies = [
"futures",
"indexmap 2.2.6",
"nix 0.28.0",
"tokio",
"tracing",
"windows 0.56.0",
]
[[package]] [[package]]
name = "prodash" name = "prodash"
version = "26.2.2" version = "26.2.2"
@ -3456,9 +3464,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.35.1" version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -3546,7 +3554,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap 2.1.0", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
"winnow", "winnow",
] ]
@ -3557,7 +3565,7 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [ dependencies = [
"indexmap 2.1.0", "indexmap 2.2.6",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@ -3992,7 +4000,6 @@ dependencies = [
"async-priority-channel", "async-priority-channel",
"async-recursion", "async-recursion",
"atomic-take", "atomic-take",
"command-group 5.0.1",
"futures", "futures",
"ignore-files", "ignore-files",
"miette", "miette",
@ -4000,6 +4007,7 @@ dependencies = [
"normalize-path", "normalize-path",
"notify", "notify",
"once_cell", "once_cell",
"process-wrap",
"project-origins", "project-origins",
"thiserror", "thiserror",
"tokio", "tokio",
@ -4022,7 +4030,6 @@ dependencies = [
"clap_complete_nushell", "clap_complete_nushell",
"clap_mangen", "clap_mangen",
"clearscreen", "clearscreen",
"command-group 2.1.0",
"console-subscriber", "console-subscriber",
"dirs 5.0.1", "dirs 5.0.1",
"embed-resource", "embed-resource",
@ -4135,9 +4142,9 @@ name = "watchexec-supervisor"
version = "1.0.3" version = "1.0.3"
dependencies = [ dependencies = [
"boxcar", "boxcar",
"command-group 5.0.1",
"futures", "futures",
"nix 0.27.1", "nix 0.27.1",
"process-wrap",
"tokio", "tokio",
"tracing", "tracing",
"watchexec-events", "watchexec-events",
@ -4217,7 +4224,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [ dependencies = [
"windows-core 0.52.0", "windows-core 0.52.0",
"windows-targets 0.52.0", "windows-targets 0.52.5",
]
[[package]]
name = "windows"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [
"windows-core 0.56.0",
"windows-targets 0.52.5",
] ]
[[package]] [[package]]
@ -4235,7 +4252,60 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [ dependencies = [
"windows-targets 0.52.0", "windows-targets 0.52.5",
]
[[package]]
name = "windows-core"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd"
dependencies = [
"windows-result",
"windows-targets 0.52.5",
]
[[package]]
name = "windows-core"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-targets 0.52.5",
]
[[package]]
name = "windows-implement"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.47",
]
[[package]]
name = "windows-interface"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.47",
]
[[package]]
name = "windows-result"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b"
dependencies = [
"windows-targets 0.52.5",
] ]
[[package]] [[package]]
@ -4253,7 +4323,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.0", "windows-targets 0.52.5",
] ]
[[package]] [[package]]
@ -4273,17 +4343,18 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.0", "windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.0", "windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.0", "windows_i686_gnu 0.52.5",
"windows_i686_msvc 0.52.0", "windows_i686_gnullvm",
"windows_x86_64_gnu 0.52.0", "windows_i686_msvc 0.52.5",
"windows_x86_64_gnullvm 0.52.0", "windows_x86_64_gnu 0.52.5",
"windows_x86_64_msvc 0.52.0", "windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
] ]
[[package]] [[package]]
@ -4294,9 +4365,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -4306,9 +4377,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -4318,9 +4389,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -4330,9 +4407,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -4342,9 +4419,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -4354,9 +4431,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -4366,9 +4443,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.0" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]] [[package]]
name = "winnow" name = "winnow"

View File

@ -42,10 +42,6 @@ tracing-test = "0.1"
uuid = { workspace = true, features = [ "v4", "fast-rng" ] } uuid = { workspace = true, features = [ "v4", "fast-rng" ] }
rand = { workspace = true } rand = { workspace = true }
[dependencies.command-group]
version = "2.1.0"
features = ["with-tokio"]
[dependencies.clap] [dependencies.clap]
version = "4.4.7" version = "4.4.7"
features = ["cargo", "derive", "env", "wrap_help"] features = ["cargo", "derive", "env", "wrap_help"]

View File

@ -232,10 +232,16 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
if let Some(ref workdir) = workdir.as_ref() { if let Some(ref workdir) = workdir.as_ref() {
debug!(?workdir, "set command workdir"); debug!(?workdir, "set command workdir");
command.current_dir(workdir); command.command_mut().current_dir(workdir);
} }
emit_events_to_command(command, events, emit_file, emit_events_to, add_envs); emit_events_to_command(
command.command_mut(),
events,
emit_file,
emit_events_to,
add_envs,
);
}); });
let show_events = || { let show_events = || {

View File

@ -2,6 +2,8 @@
## Next (YYYY-MM-DD) ## Next (YYYY-MM-DD)
- Deps: replace command-group with process-wrap (in supervisor, but has flow-on effects)
## v3.0.1 (2023-11-29) ## v3.0.1 (2023-11-29)
- Deps: watchexec-events and watchexec-signals after major bump and yank - Deps: watchexec-events and watchexec-signals after major bump and yank

View File

@ -26,9 +26,9 @@ once_cell = "1.8.0"
thiserror = "1.0.44" thiserror = "1.0.44"
normalize-path = "0.2.0" normalize-path = "0.2.0"
[dependencies.command-group] [dependencies.process-wrap]
version = "5.0.1" version = "8.0.0"
features = ["with-tokio"] features = ["tokio1"]
[dependencies.watchexec-events] [dependencies.watchexec-events]
version = "2.0.1" version = "2.0.1"

View File

@ -68,7 +68,7 @@ async fn main() -> Result<()> {
job.set_spawn_hook(|cmd, _| { job.set_spawn_hook(|cmd, _| {
use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal}; use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal};
unsafe { unsafe {
cmd.pre_exec(|| { cmd.command_mut().pre_exec(|| {
let mut newset = SigSet::empty(); let mut newset = SigSet::empty();
newset.add(Signal::SIGINT); newset.add(Signal::SIGINT);
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?; sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?;

View File

@ -47,7 +47,7 @@ async fn main() -> Result<()> {
job.set_spawn_hook(|cmd, _| { job.set_spawn_hook(|cmd, _| {
use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal}; use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal};
unsafe { unsafe {
cmd.pre_exec(|| { cmd.command_mut().pre_exec(|| {
let mut newset = SigSet::empty(); let mut newset = SigSet::empty();
newset.add(Signal::SIGINT); newset.add(Signal::SIGINT);
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?; sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?;

View File

@ -140,7 +140,8 @@ impl Watchexec {
let notify = Arc::new(Notify::new()); let notify = Arc::new(Notify::new());
let start_lock = notify.clone(); let start_lock = notify.clone();
let (ev_s, ev_r) = priority::bounded(config.event_channel_size.try_into().unwrap_or(u64::MAX)); let (ev_s, ev_r) =
priority::bounded(config.event_channel_size.try_into().unwrap_or(u64::MAX));
let event_input = ev_s.clone(); let event_input = ev_s.clone();
trace!("creating main task"); trace!("creating main task");

View File

@ -2,6 +2,8 @@
## Next (YYYY-MM-DD) ## Next (YYYY-MM-DD)
- Deps: replace command-group with process-wrap
## v1.0.3 (2023-12-19) ## v1.0.3 (2023-12-19)
- Fix Start executing even when the job is running. - Fix Start executing even when the job is running.

View File

@ -18,9 +18,9 @@ edition = "2021"
futures = "0.3.29" futures = "0.3.29"
tracing = "0.1.40" tracing = "0.1.40"
[dependencies.command-group] [dependencies.process-wrap]
version = "5.0.1" version = "8.0.0"
features = ["with-tokio"] features = ["reset-sigmask", "tokio1"]
[dependencies.tokio] [dependencies.tokio]
version = "1.33.0" version = "1.33.0"

View File

@ -61,6 +61,14 @@ pub struct SpawnOptions {
/// [Job Objects]: https://en.wikipedia.org/wiki/Object_Manager_(Windows) /// [Job Objects]: https://en.wikipedia.org/wiki/Object_Manager_(Windows)
pub grouped: bool, pub grouped: bool,
/// Run the program in a new session.
///
/// This will use Unix [sessions]. On Windows, this is not supported. This
/// implies `grouped: true`.
///
/// [sessions]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsid.html
pub session: bool,
/// Reset the signal mask of the process before we spawn it. /// Reset the signal mask of the process before we spawn it.
/// ///
/// By default, the signal mask of the process is inherited from the parent process. This means /// By default, the signal mask of the process is inherited from the parent process. This means

View File

@ -1,17 +1,17 @@
use std::fmt; use std::fmt;
use process_wrap::tokio::{KillOnDrop, TokioCommandWrap};
use tokio::process::Command as TokioCommand; use tokio::process::Command as TokioCommand;
use tracing::trace; use tracing::trace;
use super::{Command, Program}; use super::{Command, Program, SpawnOptions};
impl Command { impl Command {
/// Obtain a [`tokio::process::Command`]. /// Obtain a [`process_wrap::tokio::TokioCommandWrap`].
pub fn to_spawnable(&self) -> TokioCommand { pub fn to_spawnable(&self) -> TokioCommandWrap {
trace!(program=?self.program, "constructing command"); trace!(program=?self.program, "constructing command");
#[cfg_attr(not(unix), allow(unused_mut))] let cmd = match &self.program {
let mut cmd = match &self.program {
Program::Exec { prog, args, .. } => { Program::Exec { prog, args, .. } => {
let mut c = TokioCommand::new(prog); let mut c = TokioCommand::new(prog);
c.args(args); c.args(args);
@ -56,19 +56,28 @@ impl Command {
} }
}; };
let mut cmd = TokioCommandWrap::from(cmd);
cmd.wrap(KillOnDrop);
match self.options {
#[cfg(unix)]
SpawnOptions { session: true, .. } => {
cmd.wrap(process_wrap::tokio::ProcessSession);
}
#[cfg(unix)]
SpawnOptions { grouped: true, .. } => {
cmd.wrap(process_wrap::tokio::ProcessGroup::leader());
}
#[cfg(windows)]
SpawnOptions { grouped: true, .. } | SpawnOptions { session: true, .. } => {
cmd.wrap(process_wrap::tokio::JobObject);
}
_ => {}
}
#[cfg(unix)] #[cfg(unix)]
if self.options.reset_sigmask { if self.options.reset_sigmask {
use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow}; cmd.wrap(process_wrap::tokio::ResetSigmask);
unsafe {
cmd.pre_exec(|| {
let mut oldset = SigSet::empty();
let newset = SigSet::all();
trace!(unblocking=?newset, "resetting process sigmask");
sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&newset), Some(&mut oldset))?;
trace!(?oldset, "sigmask reset");
Ok(())
});
}
} }
cmd cmd

View File

@ -1,6 +1,6 @@
use std::{future::Future, sync::Arc, time::Duration}; use std::{future::Future, sync::Arc, time::Duration};
use tokio::process::Command as TokioCommand; use process_wrap::tokio::TokioCommandWrap;
use watchexec_signals::Signal; use watchexec_signals::Signal;
use crate::{command::Command, errors::SyncIoError, flag::Flag}; use crate::{command::Command, errors::SyncIoError, flag::Flag};
@ -289,11 +289,11 @@ impl Job {
/// Set the spawn hook. /// Set the spawn hook.
/// ///
/// The hook will be called once per process spawned, before the process is spawned. It's given /// The hook will be called once per process spawned, before the process is spawned. It's given
/// a mutable reference to the [`tokio::process::Command`] and some context; it can modify the /// a mutable reference to the [`process_wrap::tokio::TokioCommandWrap`] and some context; it
/// command as it sees fit. /// can modify or further [wrap](process_wrap) the command as it sees fit.
pub fn set_spawn_hook( pub fn set_spawn_hook(
&self, &self,
fun: impl Fn(&mut TokioCommand, &JobTaskContext<'_>) + Send + Sync + 'static, fun: impl Fn(&mut TokioCommandWrap, &JobTaskContext<'_>) + Send + Sync + 'static,
) -> Ticket { ) -> Ticket {
self.control(Control::SetSyncSpawnHook(Arc::new(fun))) self.control(Control::SetSyncSpawnHook(Arc::new(fun)))
} }
@ -301,8 +301,8 @@ impl Job {
/// Set the spawn hook (async version). /// Set the spawn hook (async version).
/// ///
/// The hook will be called once per process spawned, before the process is spawned. It's given /// The hook will be called once per process spawned, before the process is spawned. It's given
/// a mutable reference to the [`tokio::process::Command`] and some context; it can modify the /// a mutable reference to the [`process_wrap::tokio::TokioCommandWrap`] and some context; it
/// command as it sees fit. /// can modify or further [wrap](process_wrap) the command as it sees fit.
/// ///
/// A gotcha when using this method is that the future returned by the function can live longer /// A gotcha when using this method is that the future returned by the function can live longer
/// than the references it was given, so you can't bring the command or context into the async /// than the references it was given, so you can't bring the command or context into the async
@ -313,7 +313,10 @@ impl Job {
/// spawn hooks that can't be done in the simpler sync version. /// spawn hooks that can't be done in the simpler sync version.
pub fn set_spawn_async_hook( pub fn set_spawn_async_hook(
&self, &self,
fun: impl (Fn(&mut TokioCommand, &JobTaskContext<'_>) -> Box<dyn Future<Output = ()> + Send + Sync>) fun: impl (Fn(
&mut TokioCommandWrap,
&JobTaskContext<'_>,
) -> Box<dyn Future<Output = ()> + Send + Sync>)
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,

View File

@ -1,8 +1,6 @@
use std::{sync::Arc, time::Instant}; use std::{sync::Arc, time::Instant};
#[cfg(not(test))] use process_wrap::tokio::{TokioChildWrapper, TokioCommandWrap};
use command_group::{tokio::ErasedChild, AsyncCommandGroup};
use tokio::process::Command as TokioCommand;
use tracing::trace; use tracing::trace;
use watchexec_events::ProcessEnd; use watchexec_events::ProcessEnd;
@ -33,7 +31,7 @@ pub enum CommandState {
/// The child process. /// The child process.
#[cfg(not(test))] #[cfg(not(test))]
child: ErasedChild, child: Box<dyn TokioChildWrapper>,
/// The time at which the process was spawned. /// The time at which the process was spawned.
started: Instant, started: Instant,
@ -75,7 +73,7 @@ impl CommandState {
pub(crate) fn spawn( pub(crate) fn spawn(
&mut self, &mut self,
command: Arc<Command>, command: Arc<Command>,
mut spawnable: TokioCommand, mut spawnable: TokioCommandWrap,
) -> std::io::Result<bool> { ) -> std::io::Result<bool> {
if let Self::Running { .. } = self { if let Self::Running { .. } = self {
trace!("command running, not spawning again"); trace!("command running, not spawning again");
@ -88,11 +86,7 @@ impl CommandState {
let child = super::TestChild::new(command)?; let child = super::TestChild::new(command)?;
#[cfg(not(test))] #[cfg(not(test))]
let child = if command.options.grouped { let child = spawnable.spawn()?;
ErasedChild::Grouped(spawnable.group().kill_on_drop(true).spawn()?)
} else {
ErasedChild::Ungrouped(spawnable.kill_on_drop(true).spawn()?)
};
*self = Self::Running { *self = Self::Running {
child, child,
@ -136,7 +130,7 @@ impl CommandState {
pub(crate) async fn wait(&mut self) -> std::io::Result<bool> { pub(crate) async fn wait(&mut self) -> std::io::Result<bool> {
if let Self::Running { child, started } = self { if let Self::Running { child, started } = self {
let end = child.wait().await?; let end = Box::into_pin(child.wait()).await?;
*self = Self::Finished { *self = Self::Finished {
status: end.into(), status: end.into(),
started: *started, started: *started,

View File

@ -1,6 +1,7 @@
use std::{future::Future, mem::take, sync::Arc, time::Instant}; use std::{future::Future, mem::take, sync::Arc, time::Instant};
use tokio::{process::Command as TokioCommand, select, task::JoinHandle}; use process_wrap::tokio::TokioCommandWrap;
use tokio::{select, task::JoinHandle};
use tracing::{instrument, trace, trace_span, Instrument}; use tracing::{instrument, trace, trace_span, Instrument};
use watchexec_signals::Signal; use watchexec_signals::Signal;
@ -156,9 +157,9 @@ pub fn start_job(command: Arc<Command>) -> (Job, JoinHandle<()>) {
Control::Stop => { Control::Stop => {
if let CommandState::Running { child, started, .. } = &mut command_state { if let CommandState::Running { child, started, .. } = &mut command_state {
trace!("stopping child"); trace!("stopping child");
try_with_handler!(child.kill().await); try_with_handler!(Box::into_pin(child.kill()).await);
trace!("waiting on child"); trace!("waiting on child");
let status = try_with_handler!(child.wait().await); let status = try_with_handler!(Box::into_pin(child.wait()).await);
trace!(?status, "got child end status"); trace!(?status, "got child end status");
command_state = CommandState::Finished { command_state = CommandState::Finished {
@ -188,9 +189,9 @@ pub fn start_job(command: Arc<Command>) -> (Job, JoinHandle<()>) {
Control::TryRestart => { Control::TryRestart => {
if let CommandState::Running { child, started, .. } = &mut command_state { if let CommandState::Running { child, started, .. } = &mut command_state {
trace!("stopping child"); trace!("stopping child");
try_with_handler!(child.kill().await); try_with_handler!(Box::into_pin(child.kill()).await);
trace!("waiting on child"); trace!("waiting on child");
let status = try_with_handler!(child.wait().await); let status = try_with_handler!(Box::into_pin(child.wait()).await);
trace!(?status, "got child end status"); trace!(?status, "got child end status");
command_state = CommandState::Finished { command_state = CommandState::Finished {
@ -238,9 +239,9 @@ pub fn start_job(command: Arc<Command>) -> (Job, JoinHandle<()>) {
if let CommandState::Running { child, started, .. } = &mut command_state { if let CommandState::Running { child, started, .. } = &mut command_state {
trace!("stopping child forcefully"); trace!("stopping child forcefully");
try_with_handler!(child.kill().await); try_with_handler!(Box::into_pin(child.kill()).await);
trace!("waiting on child"); trace!("waiting on child");
let status = try_with_handler!(child.wait().await); let status = try_with_handler!(Box::into_pin(child.wait()).await);
trace!(?status, "got child end status"); trace!(?status, "got child end status");
command_state = CommandState::Finished { command_state = CommandState::Finished {
@ -411,15 +412,15 @@ pub type AsyncFunc = Box<
>; >;
pub type SyncSpawnHook = pub type SyncSpawnHook =
Arc<dyn Fn(&mut TokioCommand, &JobTaskContext<'_>) + Send + Sync + 'static>; Arc<dyn Fn(&mut TokioCommandWrap, &JobTaskContext<'_>) + Send + Sync + 'static>;
pub type AsyncSpawnHook = Arc< pub type AsyncSpawnHook = Arc<
dyn (Fn(&mut TokioCommand, &JobTaskContext<'_>) -> Box<dyn Future<Output = ()> + Send + Sync>) dyn (Fn(&mut TokioCommandWrap, &JobTaskContext<'_>) -> Box<dyn Future<Output = ()> + Send + Sync>)
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
>; >;
sync_async_callbox!(SpawnHook, SyncSpawnHook, AsyncSpawnHook, (command: &mut TokioCommand, context: &JobTaskContext<'_>)); sync_async_callbox!(SpawnHook, SyncSpawnHook, AsyncSpawnHook, (command: &mut TokioCommandWrap, context: &JobTaskContext<'_>));
pub type SyncErrorHandler = Arc<dyn Fn(SyncIoError) + Send + Sync + 'static>; pub type SyncErrorHandler = Arc<dyn Fn(SyncIoError) + Send + Sync + 'static>;
pub type AsyncErrorHandler = Arc< pub type AsyncErrorHandler = Arc<
@ -432,8 +433,8 @@ sync_async_callbox!(ErrorHandler, SyncErrorHandler, AsyncErrorHandler, (error: S
#[instrument(level = "trace")] #[instrument(level = "trace")]
async fn signal_child( async fn signal_child(
signal: Signal, signal: Signal,
#[cfg(not(test))] child: &mut Box<dyn process_wrap::tokio::TokioChildWrapper>,
#[cfg(test)] child: &mut super::TestChild, #[cfg(test)] child: &mut super::TestChild,
#[cfg(not(test))] child: &mut command_group::tokio::ErasedChild,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
#[cfg(unix)] #[cfg(unix)]
{ {
@ -442,7 +443,7 @@ async fn signal_child(
.or_else(|| Signal::Terminate.to_nix()) .or_else(|| Signal::Terminate.to_nix())
.expect("UNWRAP: guaranteed for Signal::Terminate default"); .expect("UNWRAP: guaranteed for Signal::Terminate default");
trace!(signal=?sig, "sending signal"); trace!(signal=?sig, "sending signal");
child.signal(sig)?; child.signal(sig as _)?;
} }
#[cfg(windows)] #[cfg(windows)]

View File

@ -328,6 +328,7 @@ async fn start() {
#[cfg(unix)] #[cfg(unix)]
#[tokio::test] #[tokio::test]
async fn signal_unix() { async fn signal_unix() {
use nix::sys::signal::Signal;
let (job, task) = start_job(working_command()); let (job, task) = start_job(working_command());
expect_state!(job, CommandState::Pending); expect_state!(job, CommandState::Pending);
@ -336,9 +337,9 @@ async fn signal_unix() {
job.signal(watchexec_signals::Signal::User1).await; job.signal(watchexec_signals::Signal::User1).await;
let calls = get_child(&job).await.calls; let calls = get_child(&job).await.calls;
assert!(calls assert!(calls.iter().any(
.iter() |(_, call)| matches!(call, TestChildCall::Signal(sig) if *sig == Signal::SIGUSR1 as i32)
.any(|(_, call)| matches!(call, TestChildCall::Signal(command_group::Signal::SIGUSR1)))); ));
task.abort(); task.abort();
} }
@ -553,6 +554,7 @@ async fn graceful_stop_beyond_grace() {
#[cfg(unix)] #[cfg(unix)]
{ {
use nix::sys::signal::Signal;
expect_state!( expect_state!(
job, job,
CommandState::Running { .. }, CommandState::Running { .. },
@ -562,7 +564,7 @@ async fn graceful_stop_beyond_grace() {
let calls = get_child(&job).await.calls; let calls = get_child(&job).await.calls;
assert!(calls.iter().any(|(_, call)| matches!( assert!(calls.iter().any(|(_, call)| matches!(
call, call,
TestChildCall::Signal(command_group::Signal::SIGUSR1) TestChildCall::Signal(sig) if *sig == Signal::SIGUSR1 as i32
))); )));
} }
@ -596,6 +598,7 @@ async fn graceful_restart_beyond_grace() {
#[cfg(unix)] #[cfg(unix)]
{ {
use nix::sys::signal::Signal;
expect_state!( expect_state!(
job, job,
CommandState::Running { .. }, CommandState::Running { .. },
@ -605,7 +608,7 @@ async fn graceful_restart_beyond_grace() {
let calls = get_child(&job).await.calls; let calls = get_child(&job).await.calls;
assert!(calls.iter().any(|(_, call)| matches!( assert!(calls.iter().any(|(_, call)| matches!(
call, call,
TestChildCall::Signal(command_group::Signal::SIGUSR1) TestChildCall::Signal(sig) if *sig == Signal::SIGUSR1 as i32
))); )));
} }

View File

@ -1,4 +1,5 @@
use std::{ use std::{
future::Future,
io::Result, io::Result,
path::Path, path::Path,
process::{ExitStatus, Output}, process::{ExitStatus, Output},
@ -6,8 +7,6 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
#[cfg(unix)]
use command_group::Signal;
use tokio::{sync::Mutex, time::sleep}; use tokio::{sync::Mutex, time::sleep};
use watchexec_events::ProcessEnd; use watchexec_events::ProcessEnd;
@ -35,7 +34,7 @@ impl TestChild {
} }
Ok(Self { Ok(Self {
grouped: command.options.grouped, grouped: command.options.grouped || command.options.session,
command, command,
calls: Arc::new(boxcar::Vec::new()), calls: Arc::new(boxcar::Vec::new()),
output: Arc::new(Mutex::new(None)), output: Arc::new(Mutex::new(None)),
@ -52,7 +51,7 @@ pub enum TestChildCall {
TryWait, TryWait,
Wait, Wait,
#[cfg(unix)] #[cfg(unix)]
Signal(Signal), Signal(i32),
} }
// Exact same signatures as ErasedChild // Exact same signatures as ErasedChild
@ -62,9 +61,9 @@ impl TestChild {
None None
} }
pub async fn kill(&mut self) -> Result<()> { pub fn kill(&mut self) -> Box<dyn Future<Output = Result<()>> + Send + '_> {
self.calls.push(TestChildCall::Kill); self.calls.push(TestChildCall::Kill);
Ok(()) Box::new(async { Ok(()) })
} }
pub fn start_kill(&mut self) -> Result<()> { pub fn start_kill(&mut self) -> Result<()> {
@ -96,54 +95,58 @@ impl TestChild {
.and_then(|o| o.as_ref().map(|o| o.status))) .and_then(|o| o.as_ref().map(|o| o.status)))
} }
pub async fn wait(&mut self) -> Result<ExitStatus> { pub fn wait(&mut self) -> Box<dyn Future<Output = Result<ExitStatus>> + Send + '_> {
self.calls.push(TestChildCall::Wait); self.calls.push(TestChildCall::Wait);
if let Program::Exec { prog, args } = &self.command.program { Box::new(async {
if prog == Path::new("sleep") { if let Program::Exec { prog, args } = &self.command.program {
if let Some(time) = args if prog == Path::new("sleep") {
.get(0) if let Some(time) = args
.and_then(|arg| arg.parse().ok()) .get(0)
.map(Duration::from_millis) .and_then(|arg| arg.parse().ok())
{ .map(Duration::from_millis)
if self.spawned.elapsed() < time { {
sleep(time - self.spawned.elapsed()).await; if self.spawned.elapsed() < time {
if let Ok(guard) = self.output.try_lock() { sleep(time - self.spawned.elapsed()).await;
if let Some(output) = guard.as_ref() { if let Ok(guard) = self.output.try_lock() {
return Ok(output.status); if let Some(output) = guard.as_ref() {
return Ok(output.status);
}
} }
}
return Ok(ProcessEnd::Success.into_exitstatus()); return Ok(ProcessEnd::Success.into_exitstatus());
}
} }
} }
} }
}
loop { loop {
eprintln!("[{:?}] child: output lock", Instant::now()); eprintln!("[{:?}] child: output lock", Instant::now());
let output = self.output.lock().await; let output = self.output.lock().await;
if let Some(output) = output.as_ref() { if let Some(output) = output.as_ref() {
return Ok(output.status); return Ok(output.status);
} }
eprintln!("[{:?}] child: output unlock", Instant::now()); eprintln!("[{:?}] child: output unlock", Instant::now());
sleep(Duration::from_secs(1)).await;
}
}
pub async fn wait_with_output(self) -> Result<Output> {
loop {
let mut output = self.output.lock().await;
if let Some(output) = output.take() {
return Ok(output);
} else {
sleep(Duration::from_secs(1)).await; sleep(Duration::from_secs(1)).await;
} }
} })
}
pub fn wait_with_output(self) -> Box<dyn Future<Output = Result<Output>> + Send> {
Box::new(async move {
loop {
let mut output = self.output.lock().await;
if let Some(output) = output.take() {
return Ok(output);
} else {
sleep(Duration::from_secs(1)).await;
}
}
})
} }
#[cfg(unix)] #[cfg(unix)]
pub fn signal(&self, sig: Signal) -> Result<()> { pub fn signal(&self, sig: i32) -> Result<()> {
self.calls.push(TestChildCall::Signal(sig)); self.calls.push(TestChildCall::Signal(sig));
Ok(()) Ok(())
} }

View File

@ -1,18 +1,20 @@
use command_group::AsyncCommandGroup;
use watchexec_supervisor::command::{Command, Program, Shell}; use watchexec_supervisor::command::{Command, Program, Shell};
#[tokio::test] #[tokio::test]
#[cfg(unix)] #[cfg(unix)]
async fn unix_shell_none() -> Result<(), std::io::Error> { async fn unix_shell_none() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Exec { Command {
prog: "echo".into(), program: Program::Exec {
args: vec!["hi".into()], prog: "echo".into(),
}, args: vec!["hi".into()],
options: Default::default() },
} options: Default::default()
.to_spawnable() }
.group_status() .to_spawnable()
.spawn()?
.wait()
)
.await? .await?
.success()); .success());
Ok(()) Ok(())
@ -21,16 +23,19 @@ async fn unix_shell_none() -> Result<(), std::io::Error> {
#[tokio::test] #[tokio::test]
#[cfg(unix)] #[cfg(unix)]
async fn unix_shell_sh() -> Result<(), std::io::Error> { async fn unix_shell_sh() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Shell { Command {
shell: Shell::new("sh"), program: Program::Shell {
command: "echo hi".into(), shell: Shell::new("sh"),
args: Vec::new(), command: "echo hi".into(),
}, args: Vec::new(),
options: Default::default() },
} options: Default::default()
.to_spawnable() }
.group_status() .to_spawnable()
.spawn()?
.wait()
)
.await? .await?
.success()); .success());
Ok(()) Ok(())
@ -39,16 +44,19 @@ async fn unix_shell_sh() -> Result<(), std::io::Error> {
#[tokio::test] #[tokio::test]
#[cfg(unix)] #[cfg(unix)]
async fn unix_shell_alternate() -> Result<(), std::io::Error> { async fn unix_shell_alternate() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Shell { Command {
shell: Shell::new("bash"), program: Program::Shell {
command: "echo".into(), shell: Shell::new("bash"),
args: vec!["--".into(), "hi".into()], command: "echo".into(),
}, args: vec!["--".into(), "hi".into()],
options: Default::default() },
} options: Default::default()
.to_spawnable() }
.group_status() .to_spawnable()
.spawn()?
.wait()
)
.await? .await?
.success()); .success());
Ok(()) Ok(())
@ -57,19 +65,22 @@ async fn unix_shell_alternate() -> Result<(), std::io::Error> {
#[tokio::test] #[tokio::test]
#[cfg(unix)] #[cfg(unix)]
async fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> { async fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Shell { Command {
shell: Shell { program: Program::Shell {
options: vec!["-o".into(), "errexit".into()], shell: Shell {
..Shell::new("bash") options: vec!["-o".into(), "errexit".into()],
..Shell::new("bash")
},
command: "echo hi".into(),
args: Vec::new(),
}, },
command: "echo hi".into(), options: Default::default()
args: Vec::new(), }
}, .to_spawnable()
options: Default::default() .spawn()?
} .wait()
.to_spawnable() )
.group_status()
.await? .await?
.success()); .success());
Ok(()) Ok(())
@ -78,15 +89,18 @@ async fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> {
#[tokio::test] #[tokio::test]
#[cfg(windows)] #[cfg(windows)]
async fn windows_shell_none() -> Result<(), std::io::Error> { async fn windows_shell_none() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Exec { Command {
prog: "echo".into(), program: Program::Exec {
args: vec!["hi".into()], prog: "echo".into(),
}, args: vec!["hi".into()],
options: Default::default() },
} options: Default::default()
.to_spawnable() }
.group_status() .to_spawnable()
.spawn()?
.wait()
)
.await? .await?
.success()); .success());
Ok(()) Ok(())
@ -95,16 +109,19 @@ async fn windows_shell_none() -> Result<(), std::io::Error> {
#[tokio::test] #[tokio::test]
#[cfg(windows)] #[cfg(windows)]
async fn windows_shell_cmd() -> Result<(), std::io::Error> { async fn windows_shell_cmd() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Shell { Command {
shell: Shell::cmd(), program: Program::Shell {
args: Vec::new(), shell: Shell::cmd(),
command: r#""echo" hi"#.into() args: Vec::new(),
}, command: r#""echo" hi"#.into()
options: Default::default() },
} options: Default::default()
.to_spawnable() }
.group_status() .to_spawnable()
.spawn()?
.wait()
)
.await? .await?
.success()); .success());
Ok(()) Ok(())
@ -113,16 +130,19 @@ async fn windows_shell_cmd() -> Result<(), std::io::Error> {
#[tokio::test] #[tokio::test]
#[cfg(windows)] #[cfg(windows)]
async fn windows_shell_powershell() -> Result<(), std::io::Error> { async fn windows_shell_powershell() -> Result<(), std::io::Error> {
assert!(Command { assert!(Box::into_pin(
program: Program::Shell { Command {
shell: Shell::new("pwsh.exe"), program: Program::Shell {
args: Vec::new(), shell: Shell::new("pwsh.exe"),
command: "echo hi".into() args: Vec::new(),
}, command: "echo hi".into()
options: Default::default() },
} options: Default::default()
.to_spawnable() }
.group_status() .to_spawnable()
.spawn()?
.wait()
)
.await? .await?
.success()); .success());
Ok(()) Ok(())