From 549329c1c6e47c087778309975c7577c4aecae99 Mon Sep 17 00:00:00 2001 From: Eric Mark Martin Date: Sun, 23 Feb 2025 00:32:46 -0500 Subject: [PATCH] add test with pty --- Cargo.lock | 105 +++++++++++++++++- crates/uv/Cargo.toml | 1 + crates/uv/src/lib.rs | 10 ++ crates/uv/tests/it/pip_install.rs | 64 ++++++++++- ...oject.toml`? [y__n] \342\200\272 yes.snap" | 6 + 5 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 "crates/uv/tests/it/snapshots/it__pip_install__? `pyproject.toml` looks like a local metadata file but was passed as a package name. Did you mean `-r pyproject.toml`? [y__n] \342\200\272 yes.snap" diff --git a/Cargo.lock b/Cargo.lock index edeca4dbd702..ab33d911d319 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cfg_aliases" version = "0.2.1" @@ -866,7 +872,7 @@ version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" dependencies = [ - "nix", + "nix 0.29.0", "windows-sys 0.59.0", ] @@ -980,6 +986,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dunce" version = "1.0.5" @@ -1101,6 +1113,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + [[package]] name = "filetime" version = "0.2.25" @@ -1522,7 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" dependencies = [ "cfg-if", - "nix", + "nix 0.29.0", "widestring", "windows 0.57.0", ] @@ -2310,6 +2333,18 @@ dependencies = [ "rand", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] + [[package]] name = "nix" version = "0.29.0" @@ -2318,7 +2353,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.8.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", ] @@ -2662,6 +2697,27 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "portable-pty" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.28.0", + "serial2", + "shared_library", + "shell-words", + "winapi", + "winreg 0.10.1", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2831,7 +2887,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", "once_cell", "socket2", @@ -3554,6 +3610,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serial2" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd0c773455b60177d1abe4c739cbfa316c4f2f0ef37465befcb72e8a15cdd02" +dependencies = [ + "cfg-if", + "libc", + "winapi", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3574,12 +3641,28 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + [[package]] name = "shell-escape" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shellexpand" version = "3.1.0" @@ -4549,9 +4632,10 @@ dependencies = [ "itertools 0.14.0", "jiff", "miette", - "nix", + "nix 0.29.0", "owo-colors", "petgraph", + "portable-pty", "predicates", "regex", "reqwest", @@ -5682,7 +5766,7 @@ dependencies = [ "tracing", "uv-fs", "uv-static", - "winreg", + "winreg 0.53.0", ] [[package]] @@ -6510,6 +6594,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.53.0" diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 61c66bc4f08e..3cf656b2f633 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -115,6 +115,7 @@ flate2 = { workspace = true, default-features = false } ignore = { version = "0.4.23" } indoc = { workspace = true } insta = { version = "1.40.0", features = ["filters", "json"] } +portable-pty = "0.9.0" predicates = { version = "3.1.2" } regex = { workspace = true } reqwest = { workspace = true, features = ["blocking"], default-features = false } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 85b8dcc09394..282c0403c768 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -299,6 +299,16 @@ async fn run(mut cli: Cli) -> Result { anstream::ColorChoice::write_global(globals.color.into()); + (match globals.color { + uv_cli::ColorChoice::Auto => None, + uv_cli::ColorChoice::Always => Some(true), + uv_cli::ColorChoice::Never => Some(false), + }) + .inspect(|colors_enabled| { + console::set_colors_enabled(*colors_enabled); + console::set_colors_enabled_stderr(*colors_enabled); + }); + miette::set_hook(Box::new(|_| { Box::new( miette::MietteHandlerOpts::new() diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index f3a12c9a6ebd..065859c73f1e 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::io::{Cursor, Read}; use std::process::Command; use anyhow::Result; @@ -9097,3 +9097,65 @@ fn unsupported_git_scheme() { "### ); } + +#[test] +fn ctrl_c_install_confirmation_prompt() -> Result<()> { + let pty_sys = portable_pty::native_pty_system(); + + let pair = pty_sys.openpty(portable_pty::PtySize::default())?; + + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.touch()?; + + let mut cmd = context.pip_install(); + cmd.arg("pyproject.toml"); + cmd.args(&["--color", "never"]); + + let mut argv = Vec::new(); + + argv.push(cmd.get_program().into()); + argv.extend(cmd.get_args().map(Into::into)); + + let mut cmdbuild = portable_pty::CommandBuilder::from_argv(argv); + + for (key, value) in cmd.get_envs() { + match value { + Some(value) => { + cmdbuild.env(key, value); + } + None => { + cmdbuild.env_remove(key); + } + } + } + + match cmd.get_current_dir() { + Some(cwd) => cmdbuild.cwd(cwd), + None => cmdbuild.clear_cwd(), + }; + + let mut child = pair.slave.spawn_command(cmdbuild)?; + let mut reader = pair.master.try_clone_reader()?; + let mut writer = pair.master.take_writer()?; + + const EXPECTED: &'static str = "? `pyproject.toml` looks like a local metadata file but was passed as a package name. Did you mean `-r pyproject.toml`? [y/n] › yes"; + + let mut buffer = [0u8; EXPECTED.len()]; + reader.read_exact(&mut buffer)?; + let output = String::from_utf8_lossy(&buffer).to_string(); + + insta::assert_snapshot!(output, EXPECTED); + + // 0x03 = ETX + writer.write_all(&[0x03])?; + writer.flush()?; + + let exit_status = child.wait()?; + + let expected_exit_code = if cfg!(windows) { 0xC000_013A_u32 } else { 130 }; + assert_eq!(exit_status.exit_code(), expected_exit_code); + + Ok(()) +} diff --git "a/crates/uv/tests/it/snapshots/it__pip_install__? `pyproject.toml` looks like a local metadata file but was passed as a package name. Did you mean `-r pyproject.toml`? [y__n] \342\200\272 yes.snap" "b/crates/uv/tests/it/snapshots/it__pip_install__? `pyproject.toml` looks like a local metadata file but was passed as a package name. Did you mean `-r pyproject.toml`? [y__n] \342\200\272 yes.snap" new file mode 100644 index 000000000000..49c13b29e428 --- /dev/null +++ "b/crates/uv/tests/it/snapshots/it__pip_install__? `pyproject.toml` looks like a local metadata file but was passed as a package name. Did you mean `-r pyproject.toml`? [y__n] \342\200\272 yes.snap" @@ -0,0 +1,6 @@ +--- +source: crates/uv/tests/it/pip_install.rs +expression: EXPECTED +snapshot_kind: text +--- +? `pyproject.toml` looks like a local metadata file but was passed as a package name. Did you mean `-r pyproject.toml`? [y/n] › yes