From af02671fdb6a3f51fc56443c0bc184e158cecd3e Mon Sep 17 00:00:00 2001 From: branchseer Date: Sat, 28 Mar 2026 18:56:43 +0800 Subject: [PATCH 1/2] Add ctrl-c e2e test for SIGINT propagation to running tasks Add a `vtt exit-on-ctrlc` subcommand and e2e fixture that verifies SIGINT propagates to concurrent tasks when the user presses Ctrl+C. The test runs two packages with `vt run -r dev` (using argv spawn mode), synchronizes via milestone protocol, then sends ctrl-c and verifies both tasks handle it. Changes: - `vtt exit-on-ctrlc`: sets up ctrl-c handler, emits "ready" milestone, prints "ctrl-c received" and exits on SIGINT. On Windows, clears the inherited CONSOLE_IGNORE_CTRL_C flag (set by an ancestor process via CREATE_NEW_PROCESS_GROUP) before registering the handler. - `vt main.rs`: register no-op ctrlc handler before tokio runtime so vt survives Ctrl+C and reports actual task exit status. Use process::exit to avoid Windows runtime cleanup issues. - `ctrl-c` WriteKey variant for e2e test interactions (rename_all kebab-case) - Fix `expect_milestone` to preserve unmatched milestones in a local buffer instead of dropping them via `take_unhandled_osc_sequences` - Strip `^C` terminal echo and normalize `ctrl-c received` count in e2e redaction for cross-platform consistency - Fix incorrect comment in pty_terminal test about CTRL_C ignore flag source Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 2 + crates/pty_terminal/tests/terminal.rs | 8 +++- crates/pty_terminal_test/src/lib.rs | 40 ++++++++++++++----- crates/vite_task_bin/Cargo.toml | 2 + crates/vite_task_bin/src/main.rs | 26 +++++++++--- crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs | 40 +++++++++++++++++++ crates/vite_task_bin/src/vtt/main.rs | 6 ++- .../fixtures/ctrl-c/package.json | 4 ++ .../fixtures/ctrl-c/packages/a/package.json | 6 +++ .../fixtures/ctrl-c/packages/b/package.json | 6 +++ .../fixtures/ctrl-c/pnpm-workspace.yaml | 2 + .../fixtures/ctrl-c/snapshots.toml | 18 +++++++++ .../ctrl-c terminates running tasks.snap | 18 +++++++++ .../fixtures/ctrl-c/vite-task.json | 3 ++ .../tests/e2e_snapshots/redact.rs | 21 ++++++++++ 15 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json diff --git a/Cargo.lock b/Cargo.lock index 9e08fd4d..d9f4e2dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3980,12 +3980,14 @@ dependencies = [ "clap", "cow-utils", "cp_r", + "ctrlc", "insta", "jsonc-parser", "libc", "notify", "pty_terminal", "pty_terminal_test", + "pty_terminal_test_client", "regex", "serde", "serde_json", diff --git a/crates/pty_terminal/tests/terminal.rs b/crates/pty_terminal/tests/terminal.rs index aebbcb5d..ec6bcc49 100644 --- a/crates/pty_terminal/tests/terminal.rs +++ b/crates/pty_terminal/tests/terminal.rs @@ -305,8 +305,12 @@ fn send_ctrl_c_interrupts_process() { // On macOS/Windows, use ctrlc which works fine (no .init_array/musl issue). #[cfg(not(target_os = "linux"))] { - // On Windows, clear the "ignore CTRL_C" flag set by Rust runtime - // so that CTRL_C_EVENT reaches the ctrlc handler. + // On Windows, an ancestor process may have been created with + // CREATE_NEW_PROCESS_GROUP, which implicitly sets the per-process + // CTRL_C ignore flag (CONSOLE_IGNORE_CTRL_C in PEB ConsoleFlags). + // This flag is inherited by all descendants and silently drops + // CTRL_C_EVENT before it reaches registered handlers. Clear it. + // Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags #[cfg(windows)] { // SAFETY: Declaring correct signature for SetConsoleCtrlHandler from kernel32. diff --git a/crates/pty_terminal_test/src/lib.rs b/crates/pty_terminal_test/src/lib.rs index b0cbe0d1..04adee8c 100644 --- a/crates/pty_terminal_test/src/lib.rs +++ b/crates/pty_terminal_test/src/lib.rs @@ -1,4 +1,7 @@ -use std::io::{BufReader, Read}; +use std::{ + collections::VecDeque, + io::{BufReader, Read}, +}; pub use portable_pty::CommandBuilder; use pty_terminal::terminal::{PtyReader, Terminal}; @@ -25,6 +28,8 @@ pub struct TestTerminal { pub struct Reader { pty: BufReader, child_handle: ChildHandle, + /// OSC sequences taken from the PTY but not yet consumed by `expect_milestone`. + pending_osc: VecDeque>>, } impl TestTerminal { @@ -37,7 +42,11 @@ impl TestTerminal { let Terminal { pty_reader, pty_writer, child_handle, .. } = Terminal::spawn(size, cmd)?; Ok(Self { writer: pty_writer, - reader: Reader { pty: BufReader::new(pty_reader), child_handle: child_handle.clone() }, + reader: Reader { + pty: BufReader::new(pty_reader), + child_handle: child_handle.clone(), + pending_osc: VecDeque::new(), + }, child_handle, }) } @@ -71,15 +80,24 @@ impl Reader { let mut buf = [0u8; 4096]; loop { - let found = self - .pty - .get_ref() - .take_unhandled_osc_sequences() - .into_iter() - .filter_map(|params| { - pty_terminal_test_client::decode_milestone_from_osc8_params(¶ms) - }) - .any(|decoded| decoded == name); + // Drain new sequences from the PTY into our local buffer. + self.pending_osc.append(&mut self.pty.get_ref().take_unhandled_osc_sequences()); + + // Scan for the first matching milestone, keeping the rest. + let mut found = false; + let mut remaining = VecDeque::with_capacity(self.pending_osc.len()); + for params in self.pending_osc.drain(..) { + if !found + && pty_terminal_test_client::decode_milestone_from_osc8_params(¶ms) + .is_some_and(|decoded| decoded == name) + { + found = true; + continue; + } + remaining.push_back(params); + } + self.pending_osc = remaining; + if found { return self.screen_contents(); } diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml index 4fbc99e2..18e45ee5 100644 --- a/crates/vite_task_bin/Cargo.toml +++ b/crates/vite_task_bin/Cargo.toml @@ -16,8 +16,10 @@ path = "src/vtt/main.rs" [dependencies] anyhow = { workspace = true } +ctrlc = { workspace = true } libc = { workspace = true } notify = { workspace = true } +pty_terminal_test_client = { workspace = true, features = ["testing"] } async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } jsonc-parser = { workspace = true } diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index a0569897..2fe73c00 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -1,13 +1,27 @@ -use std::process::ExitCode; - use clap::Parser as _; use vite_task::{Command, ExitStatus, Session}; use vite_task_bin::OwnedSessionConfig; -#[tokio::main] -async fn main() -> anyhow::Result { - let exit_status = run().await?; - Ok(exit_status.0.into()) +fn main() -> ! { + // Ignore SIGINT/CTRL_C before the tokio runtime starts. Child tasks + // receive the signal directly from the terminal driver and handle it + // themselves. This lets the runner wait for tasks to exit and report + // their actual exit status rather than being killed mid-flight. + let _ = ctrlc::set_handler(|| {}); + + let exit_code: i32 = + tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async { + match run().await { + Ok(status) => i32::from(status.0), + #[expect(clippy::print_stderr, reason = "top-level error reporting")] + Err(err) => { + eprintln!("Error: {err:?}"); + 1 + } + } + }); + + std::process::exit(exit_code); } async fn run() -> anyhow::Result { diff --git a/crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs b/crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs new file mode 100644 index 00000000..3eed75e8 --- /dev/null +++ b/crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs @@ -0,0 +1,40 @@ +/// exit-on-ctrlc +/// +/// Sets up a Ctrl+C handler, emits a "ready" milestone, then waits. +/// When Ctrl+C is received, prints "ctrl-c received" and exits. +pub fn run() -> Result<(), Box> { + // On Windows, an ancestor process (e.g. cargo, the test harness) may have + // been created with CREATE_NEW_PROCESS_GROUP, which implicitly calls + // SetConsoleCtrlHandler(NULL, TRUE) and sets CONSOLE_IGNORE_CTRL_C in the + // PEB's ConsoleFlags. This flag is inherited by all descendants and takes + // precedence over registered handlers — CTRL_C_EVENT is silently dropped. + // Clear it so our handler can fire. + // Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags + #[cfg(windows)] + { + // SAFETY: Passing (None, FALSE) clears the per-process CTRL_C ignore flag. + unsafe extern "system" { + fn SetConsoleCtrlHandler( + handler: Option i32>, + add: i32, + ) -> i32; + } + // SAFETY: Clearing the inherited ignore flag. + unsafe { + SetConsoleCtrlHandler(None, 0); + } + } + + ctrlc::set_handler(move || { + use std::io::Write; + let _ = write!(std::io::stdout(), "ctrl-c received"); + let _ = std::io::stdout().flush(); + std::process::exit(0); + })?; + + pty_terminal_test_client::mark_milestone("ready"); + + loop { + std::thread::park(); + } +} diff --git a/crates/vite_task_bin/src/vtt/main.rs b/crates/vite_task_bin/src/vtt/main.rs index a51014d3..527e423b 100644 --- a/crates/vite_task_bin/src/vtt/main.rs +++ b/crates/vite_task_bin/src/vtt/main.rs @@ -10,6 +10,7 @@ mod barrier; mod check_tty; mod cp; mod exit; +mod exit_on_ctrlc; mod mkdir; mod pipe_stdin; mod print; @@ -27,7 +28,7 @@ fn main() { if args.len() < 2 { eprintln!("Usage: vtt [args...]"); eprintln!( - "Subcommands: barrier, check-tty, cp, exit, mkdir, pipe-stdin, print, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, touch-file, write-file" + "Subcommands: barrier, check-tty, cp, exit, exit-on-ctrlc, mkdir, pipe-stdin, print, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, touch-file, write-file" ); std::process::exit(1); } @@ -40,7 +41,9 @@ fn main() { } "cp" => cp::run(&args[2..]), "exit" => exit::run(&args[2..]), + "exit-on-ctrlc" => exit_on_ctrlc::run(), "mkdir" => mkdir::run(&args[2..]), + "pipe-stdin" => pipe_stdin::run(&args[2..]), "print" => { print::run(&args[2..]); Ok(()) @@ -50,7 +53,6 @@ fn main() { "print-file" => print_file::run(&args[2..]), "read-stdin" => read_stdin::run(), "replace-file-content" => replace_file_content::run(&args[2..]), - "pipe-stdin" => pipe_stdin::run(&args[2..]), "rm" => rm::run(&args[2..]), "touch-file" => touch_file::run(&args[2..]), "write-file" => write_file::run(&args[2..]), diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json new file mode 100644 index 00000000..a9a08d6f --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json @@ -0,0 +1,4 @@ +{ + "name": "ctrl-c-test", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json new file mode 100644 index 00000000..afec67f2 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json @@ -0,0 +1,6 @@ +{ + "name": "@ctrl-c/a", + "scripts": { + "dev": "vtt exit-on-ctrlc" + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json new file mode 100644 index 00000000..74d067b4 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json @@ -0,0 +1,6 @@ +{ + "name": "@ctrl-c/b", + "scripts": { + "dev": "vtt exit-on-ctrlc" + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml new file mode 100644 index 00000000..924b55f4 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml new file mode 100644 index 00000000..60a27c9b --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml @@ -0,0 +1,18 @@ +# Tests that Ctrl+C (SIGINT) propagates to and terminates running tasks. +# Two packages run concurrently; both set up ctrl-c handlers and emit +# a "ready" milestone. After both are ready, ctrl-c is sent. + +[[e2e]] +name = "ctrl-c terminates running tasks" +steps = [ + { argv = [ + "vt", + "run", + "-r", + "dev", + ], interactions = [ + { "expect-milestone" = "ready" }, + { "expect-milestone" = "ready" }, + { "write-key" = "ctrl-c" }, + ] }, +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap new file mode 100644 index 00000000..15c6c702 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap @@ -0,0 +1,18 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vt run -r dev +@ expect-milestone: ready +~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled +~/packages/b$ vtt exit-on-ctrlc ⊘ cache disabled +@ expect-milestone: ready +~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled +~/packages/b$ vtt exit-on-ctrlc ⊘ cache disabled +@ write-key: ctrl-c +~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled +~/packages/b$ vtt exit-on-ctrlc ⊘ cache disabled +ctrl-c received + +--- +vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json new file mode 100644 index 00000000..b39113d0 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json @@ -0,0 +1,3 @@ +{ + "cache": false +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs index b2e85f22..1e826bb1 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs @@ -101,6 +101,27 @@ pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String { let mise_warning_regex = regex::Regex::new(r"(?m)^mise WARN\s+.*\n?").unwrap(); output = mise_warning_regex.replace_all(&output, "").into_owned(); + // Remove ^C echo that Unix terminal drivers emit when ETX (0x03) is written + // to the PTY. Windows ConPTY does not echo it. + { + use cow_utils::CowUtils as _; + if let Cow::Owned(replaced) = output.as_str().cow_replace("^C", "") { + output = replaced; + } + } + + // Normalize "ctrl-c received" output: when vt and tasks all receive SIGINT, + // it's a race whether one or both tasks print before the process exits. + // Normalize to a single occurrence for stable snapshots. + { + use cow_utils::CowUtils as _; + if let Cow::Owned(replaced) = + output.as_str().cow_replace("ctrl-c receivedctrl-c received", "ctrl-c received") + { + output = replaced; + } + } + // Sort consecutive diagnostic blocks to handle non-deterministic tool output // (e.g., oxlint reports warnings in arbitrary order due to multi-threading). // Each block starts with " ! " and ends at the next empty line. From dab5d8cbecfc5e8cf922486d5200efddd2e978c6 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 29 Mar 2026 01:25:40 +0800 Subject: [PATCH 2/2] Simplify ctrl-c e2e test to single task - Revert pending_osc buffering in pty_terminal_test (unnecessary) - Remove "ctrl-c receivedctrl-c received" redaction (no longer needed) - Use single-package fixture with vite-task.json command instead of multi-package workspace with two concurrent tasks Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/pty_terminal_test/src/lib.rs | 40 +++++-------------- .../fixtures/ctrl-c/package.json | 3 +- .../fixtures/ctrl-c/packages/a/package.json | 6 --- .../fixtures/ctrl-c/packages/b/package.json | 6 --- .../fixtures/ctrl-c/pnpm-workspace.yaml | 2 - .../fixtures/ctrl-c/snapshots.toml | 6 +-- .../ctrl-c terminates running tasks.snap | 14 ++----- .../fixtures/ctrl-c/vite-task.json | 7 +++- .../tests/e2e_snapshots/redact.rs | 12 ------ 9 files changed, 22 insertions(+), 74 deletions(-) delete mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json delete mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json delete mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml diff --git a/crates/pty_terminal_test/src/lib.rs b/crates/pty_terminal_test/src/lib.rs index 04adee8c..b0cbe0d1 100644 --- a/crates/pty_terminal_test/src/lib.rs +++ b/crates/pty_terminal_test/src/lib.rs @@ -1,7 +1,4 @@ -use std::{ - collections::VecDeque, - io::{BufReader, Read}, -}; +use std::io::{BufReader, Read}; pub use portable_pty::CommandBuilder; use pty_terminal::terminal::{PtyReader, Terminal}; @@ -28,8 +25,6 @@ pub struct TestTerminal { pub struct Reader { pty: BufReader, child_handle: ChildHandle, - /// OSC sequences taken from the PTY but not yet consumed by `expect_milestone`. - pending_osc: VecDeque>>, } impl TestTerminal { @@ -42,11 +37,7 @@ impl TestTerminal { let Terminal { pty_reader, pty_writer, child_handle, .. } = Terminal::spawn(size, cmd)?; Ok(Self { writer: pty_writer, - reader: Reader { - pty: BufReader::new(pty_reader), - child_handle: child_handle.clone(), - pending_osc: VecDeque::new(), - }, + reader: Reader { pty: BufReader::new(pty_reader), child_handle: child_handle.clone() }, child_handle, }) } @@ -80,24 +71,15 @@ impl Reader { let mut buf = [0u8; 4096]; loop { - // Drain new sequences from the PTY into our local buffer. - self.pending_osc.append(&mut self.pty.get_ref().take_unhandled_osc_sequences()); - - // Scan for the first matching milestone, keeping the rest. - let mut found = false; - let mut remaining = VecDeque::with_capacity(self.pending_osc.len()); - for params in self.pending_osc.drain(..) { - if !found - && pty_terminal_test_client::decode_milestone_from_osc8_params(¶ms) - .is_some_and(|decoded| decoded == name) - { - found = true; - continue; - } - remaining.push_back(params); - } - self.pending_osc = remaining; - + let found = self + .pty + .get_ref() + .take_unhandled_osc_sequences() + .into_iter() + .filter_map(|params| { + pty_terminal_test_client::decode_milestone_from_osc8_params(¶ms) + }) + .any(|decoded| decoded == name); if found { return self.screen_contents(); } diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json index a9a08d6f..f444dee5 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/package.json @@ -1,4 +1,3 @@ { - "name": "ctrl-c-test", - "private": true + "name": "ctrl-c-test" } diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json deleted file mode 100644 index afec67f2..00000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@ctrl-c/a", - "scripts": { - "dev": "vtt exit-on-ctrlc" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json deleted file mode 100644 index 74d067b4..00000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/packages/b/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@ctrl-c/b", - "scripts": { - "dev": "vtt exit-on-ctrlc" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml deleted file mode 100644 index 924b55f4..00000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml index 60a27c9b..db217d6d 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots.toml @@ -1,6 +1,4 @@ -# Tests that Ctrl+C (SIGINT) propagates to and terminates running tasks. -# Two packages run concurrently; both set up ctrl-c handlers and emit -# a "ready" milestone. After both are ready, ctrl-c is sent. +# Tests that Ctrl+C (SIGINT) propagates to and terminates a running task. [[e2e]] name = "ctrl-c terminates running tasks" @@ -8,10 +6,8 @@ steps = [ { argv = [ "vt", "run", - "-r", "dev", ], interactions = [ - { "expect-milestone" = "ready" }, { "expect-milestone" = "ready" }, { "write-key" = "ctrl-c" }, ] }, diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap index 15c6c702..b19a6308 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/snapshots/ctrl-c terminates running tasks.snap @@ -2,17 +2,9 @@ source: crates/vite_task_bin/tests/e2e_snapshots/main.rs expression: e2e_outputs --- -> vt run -r dev +> vt run dev @ expect-milestone: ready -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -~/packages/b$ vtt exit-on-ctrlc ⊘ cache disabled -@ expect-milestone: ready -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -~/packages/b$ vtt exit-on-ctrlc ⊘ cache disabled +$ vtt exit-on-ctrlc ⊘ cache disabled @ write-key: ctrl-c -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -~/packages/b$ vtt exit-on-ctrlc ⊘ cache disabled +$ vtt exit-on-ctrlc ⊘ cache disabled ctrl-c received - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json index b39113d0..f66089b1 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl-c/vite-task.json @@ -1,3 +1,8 @@ { - "cache": false + "cache": false, + "tasks": { + "dev": { + "command": "vtt exit-on-ctrlc" + } + } } diff --git a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs index 1e826bb1..4cc3fa64 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs @@ -110,18 +110,6 @@ pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String { } } - // Normalize "ctrl-c received" output: when vt and tasks all receive SIGINT, - // it's a race whether one or both tasks print before the process exits. - // Normalize to a single occurrence for stable snapshots. - { - use cow_utils::CowUtils as _; - if let Cow::Owned(replaced) = - output.as_str().cow_replace("ctrl-c receivedctrl-c received", "ctrl-c received") - { - output = replaced; - } - } - // Sort consecutive diagnostic blocks to handle non-deterministic tool output // (e.g., oxlint reports warnings in arbitrary order due to multi-threading). // Each block starts with " ! " and ends at the next empty line.