From a2f5268e70ed3d8fc52ead8815b761d0b7a0838b Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Fri, 31 May 2024 21:30:45 +0800 Subject: [PATCH 01/15] Support tests that need a terminal emulator --- Cargo.lock | 108 +++++++++++++++++++++ Cargo.toml | 4 +- crates/nu-test-support/Cargo.toml | 3 +- crates/nu-test-support/src/lib.rs | 1 + crates/nu-test-support/src/terminal.rs | 126 +++++++++++++++++++++++++ 5 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 crates/nu-test-support/src/terminal.rs diff --git a/Cargo.lock b/Cargo.lock index 2d339ec7da..1396dc6d4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "alacritty_terminal" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5ad9e2d4c1f7e17fccec9493eeb4e9c1f00e1167519d3940272b708ed8a069" +dependencies = [ + "base64 0.22.1", + "bitflags 2.5.0", + "home", + "libc", + "log", + "miow", + "parking_lot", + "piper", + "polling", + "regex-automata", + "rustix-openpty", + "serde", + "signal-hook", + "unicode-width", + "vte 0.13.0", + "windows-sys 0.48.0", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -297,6 +321,12 @@ version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ae037714f313c1353189ead58ef9eec30a8e8dc101b2622d461418fd59e28a9" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.2.0" @@ -888,6 +918,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.8" @@ -1129,6 +1168,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "deranged" version = "0.3.11" @@ -2632,6 +2677,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "miow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "359f76430b20a79f9e20e115b3428614e654f04fab314482fc0fda0ebd3c6044" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "mockito" version = "1.4.0" @@ -2772,6 +2826,7 @@ dependencies = [ name = "nu" version = "0.94.1" dependencies = [ + "alacritty_terminal", "assert_cmd", "crossterm", "ctrlc", @@ -3304,6 +3359,7 @@ dependencies = [ name = "nu-test-support" version = "0.94.1" dependencies = [ + "alacritty_terminal", "nu-glob", "nu-path", "nu-utils", @@ -3981,6 +4037,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -4417,6 +4484,21 @@ dependencies = [ "version_check", ] +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "pori" version = "0.0.0" @@ -5123,11 +5205,23 @@ checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", + "itoa", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustix-openpty" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" +dependencies = [ + "errno", + "libc", + "rustix", +] + [[package]] name = "rustversion" version = "1.0.15" @@ -6543,6 +6637,20 @@ dependencies = [ "vte_generate_state_changes", ] +[[package]] +name = "vte" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "log", + "serde", + "utf8parse", + "vte_generate_state_changes", +] + [[package]] name = "vte_generate_state_changes" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index a8576baa95..df1ace819c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ ] [workspace.dependencies] +alacritty_terminal = "0.24" alphanumeric-sort = "1.5" ansi-str = "0.8" anyhow = "1.0.82" @@ -221,6 +222,7 @@ nix = { workspace = true, default-features = false, features = [ nu-test-support = { path = "./crates/nu-test-support", version = "0.94.1" } nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.1" } nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.1" } +alacritty_terminal = { workspace = true } assert_cmd = "2.0" dirs-next = { workspace = true } tango-bench = "0.5" @@ -305,4 +307,4 @@ bench = false # Run individual benchmarks like `cargo bench -- ` e.g. `cargo bench -- parse` [[bench]] name = "benchmarks" -harness = false \ No newline at end of file +harness = false diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index e2525d8c78..90bc9faae4 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -18,4 +18,5 @@ nu-utils = { path = "../nu-utils", version = "0.94.1" } num-format = { workspace = true } which = { workspace = true } -tempfile = { workspace = true } \ No newline at end of file +tempfile = { workspace = true } +alacritty_terminal = { workspace = true } diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs index 5319830920..07135e5ba8 100644 --- a/crates/nu-test-support/src/lib.rs +++ b/crates/nu-test-support/src/lib.rs @@ -3,6 +3,7 @@ pub mod fs; pub mod locale_override; pub mod macros; pub mod playground; +pub mod terminal; use std::process::ExitStatus; // Needs to be reexported for `nu!` macro diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs new file mode 100644 index 0000000000..852611e2f4 --- /dev/null +++ b/crates/nu-test-support/src/terminal.rs @@ -0,0 +1,126 @@ +//! Helper functions for tests that requires a terminal emulator. + +use alacritty_terminal::{ + event::{Event, EventListener, WindowSize}, + grid::Indexed, + term::{test::TermSize, Config}, + tty::{self, EventedReadWrite, Options, Pty, Shell}, + vte::ansi::{Processor, StdSyncHandler}, + Term, +}; +use std::{ + collections::HashMap, + io::{ErrorKind, Read, Write}, + path::PathBuf, + sync::mpsc, + time::Duration, +}; + +pub struct EventProxy(mpsc::Sender); + +impl EventListener for EventProxy { + fn send_event(&self, event: Event) { + let _ = self.0.send(event); + } +} + +/// Creates a 24x80 terminal with default configurations. Returns the terminal +/// and a `mpsc::Receiver` that receives terminal events. +pub fn default_terminal() -> (Term, mpsc::Receiver) { + let config = Config::default(); + let size = TermSize { + screen_lines: 24, + columns: 80, + }; + let (tx, rx) = mpsc::channel(); + (Term::new(config, &size, EventProxy(tx)), rx) +} + +/// Creates a PTY and connect the slave end to a Nushell process. If `pwd` is +/// None, the Nushell process will inherit PWD from the current process. +pub fn pty_with_nushell(args: Vec, pwd: Option) -> Pty { + let executable = crate::fs::executable_path().to_string_lossy().to_string(); + let options = Options { + shell: Some(Shell::new(executable, args)), + working_directory: pwd, + hold: false, + env: HashMap::new(), + }; + let window_size = WindowSize { + num_lines: 24, + num_cols: 80, + cell_width: 0, + cell_height: 0, + }; + tty::new(&options, window_size, 0).unwrap() +} + +/// Reads from `pty` until no more data is available. Will periodically call +/// `event_handler` to handle terminal events. +pub fn read_to_end( + terminal: &mut Term, + pty: &mut Pty, + events: &mut mpsc::Receiver, + mut event_handler: impl FnMut(&mut Term, &mut Pty, Event), +) { + let mut parser: Processor = Processor::new(); + loop { + // Read from the PTY. + let mut buf = [0; 512]; + match pty.reader().read(&mut buf) { + Ok(n) => { + if n == 0 { + return; + } else { + // Update the terminal state. + for byte in &buf[..n] { + parser.advance(terminal, *byte); + } + + // Handle terminal events. + while let Ok(event) = events.try_recv() { + event_handler(terminal, pty, event); + } + + // Poll again after 100ms. The delay is necessary so that + // the child process can respond to any new data we might + // have sent in the event handler. + std::thread::sleep(Duration::from_millis(100)); + } + } + Err(err) => { + if let ErrorKind::Interrupted = err.kind() { + // retry + } else { + return; + } + } + } + } +} + +/// An event handler that only responds to `Event::PtyWrite`. This is the +/// minimum amount of event handling you need to get Nushell working. +pub fn pty_write_handler(_terminal: &mut Term, pty: &mut Pty, event: Event) { + if let Event::PtyWrite(text) = event { + pty.writer().write_all(text.as_bytes()).unwrap(); + } +} + +/// Extracts the current cursor position. +pub fn extract_cursor(terminal: &Term) -> (usize, usize) { + let cursor = terminal.grid().cursor.point; + (cursor.line.0 as usize, cursor.column.0 as usize) +} + +/// Extracts all visible text, ignoring text styles. +pub fn extract_text(terminal: &Term) -> Vec { + let mut text: Vec = vec![]; + for Indexed { point, cell } in terminal.grid().display_iter() { + if point.column == 0 { + text.push(String::new()); + } + text.last_mut().unwrap().push(cell.c); + } + text +} From 9f5995e21f043e7e4f21b068e16a76e489c7f7ef Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Fri, 31 May 2024 21:30:54 +0800 Subject: [PATCH 02/15] Add test for PWD-aware command hints --- tests/main.rs | 1 + tests/terminal/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/terminal/mod.rs diff --git a/tests/main.rs b/tests/main.rs index 6824f26f13..dddf851151 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -14,3 +14,4 @@ mod plugins; mod repl; mod scope; mod shell; +mod terminal; diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs new file mode 100644 index 0000000000..deacd1a1e8 --- /dev/null +++ b/tests/terminal/mod.rs @@ -0,0 +1,49 @@ +use alacritty_terminal::tty::EventedReadWrite; +use nu_test_support::terminal::{ + default_terminal, extract_cursor, extract_text, pty_with_nushell, pty_write_handler, + read_to_end, +}; +use std::{io::Write, time::Duration}; + +#[test] +fn command_hints_are_pwd_aware() { + // PWD-aware command hints require setting history file format to "sqlite". + let nu_config = tempfile::NamedTempFile::new().unwrap(); + let nu_config_string = nu_config.path().to_string_lossy().to_string(); + std::fs::write( + &nu_config, + "$env.config = { history: { file_format: 'sqlite' } }", + ) + .unwrap(); + + // Setup a directory with two sub-directories in it. + let cwd = tempfile::tempdir().unwrap(); + std::fs::create_dir(cwd.path().join("foo")).unwrap(); + std::fs::create_dir(cwd.path().join("bar")).unwrap(); + + // Create the PTY and the terminal. + let mut pty = pty_with_nushell( + vec!["--config".to_string(), nu_config_string], + Some(cwd.path().to_path_buf()), + ); + let (mut term, mut events) = default_terminal(); + + // Wait for Nushell to initalize. + std::thread::sleep(Duration::from_millis(500)); + + pty.writer().write_all(b"cd foo\r").unwrap(); + pty.writer().write_all(b"print 'FOO'\r").unwrap(); + pty.writer().write_all(b"cd ../bar\r").unwrap(); + pty.writer().write_all(b"print 'BAR'\r").unwrap(); + pty.writer().write_all(b"cd ../foo\r").unwrap(); + // Type "print", then press the right arrow, then press Enter. + pty.writer().write_all(b"print\x1b[C\r").unwrap(); + + // Read the response from Nushell. + read_to_end(&mut term, &mut pty, &mut events, pty_write_handler); + + // Examine the terminal state. + let (row, _col) = extract_cursor(&term); + let text = extract_text(&term); + assert!(text[row - 2].contains("print 'FOO'")); +} From e97aa2af0e7899bd76853b398102281d2b30060a Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sat, 1 Jun 2024 20:06:11 +0000 Subject: [PATCH 03/15] Add test for auto-cd --- tests/terminal/mod.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index deacd1a1e8..457c1ebd1e 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -5,6 +5,38 @@ use nu_test_support::terminal::{ }; use std::{io::Write, time::Duration}; +#[test] +fn auto_cd_works() { + // Setup a directory with a sub-directory in it. + let cwd = tempfile::tempdir().unwrap(); + std::fs::create_dir(cwd.path().join("foo")).unwrap(); + + // Create the PTY and the terminal. + let mut pty = pty_with_nushell( + vec!["--no-config-file".to_string()], + Some(cwd.path().to_path_buf()), + ); + let (mut term, mut events) = default_terminal(); + + // Wait for Nushell to initalize. + std::thread::sleep(Duration::from_millis(500)); + + #[cfg(windows)] + pty.writer().write_all(b".\\foo\r").unwrap(); + #[cfg(unix)] + pty.writer().write_all(b"./foo\r").unwrap(); + + pty.writer().write_all(b"pwd\r").unwrap(); + + // Read the response from Nushell. + read_to_end(&mut term, &mut pty, &mut events, pty_write_handler); + + // Examine the terminal state. + let (row, _col) = extract_cursor(&term); + let text = extract_text(&term); + assert!(text[row - 1].contains("foo")); +} + #[test] fn command_hints_are_pwd_aware() { // PWD-aware command hints require setting history file format to "sqlite". From 461b69c92e326c40f5f96fdb642d7fe8a40bd79c Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sun, 2 Jun 2024 19:09:57 +0800 Subject: [PATCH 04/15] Update doc --- crates/nu-test-support/src/terminal.rs | 37 +++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs index 852611e2f4..13ab86a6dc 100644 --- a/crates/nu-test-support/src/terminal.rs +++ b/crates/nu-test-support/src/terminal.rs @@ -1,4 +1,39 @@ -//! Helper functions for tests that requires a terminal emulator. +//! Helper functions for tests that requires a terminal emulator. Terminal +//! emulation is supported by the `alacritty_terminal` crate. +//! +//! Here's the general process of writing a test with a terminal emulator. This +//! module provides helper functions for common cases, but you can always do it +//! on your own. Examples can be found in `tests/terminal`. +//! +//! Step 1: Create an `alacritty_terminal::Term` instance. Here you can +//! configure window size and scrollback buffer size, etc. `default_terminal()` +//! will create one for you with sensible defaults. +//! +//! Step 2: Create a PTY and spawn a Nushell process to the slave end. +//! `pty_with_nushell()` will do that for you. It's probably a good idea to pass +//! `--no-config-file` as argument to Nushell. +//! +//! Step 3: Wait for Nushell to initialize (sleeping for 500ms should do). On +//! Linux, trying to write to the PTY before Nushell finishes initialization +//! appears to succeed, but the data will be lost. I'm not sure if this is a bug +//! or just weird behavior of Linux PTY. It's not necessary on Windows, but it +//! won't hurt either. +//! +//! Step 4: Write data to the PTY. Any data you sent will appear to Nushell as +//! if they were typed in a terminal. ANSI escape codes are used for special +//! keystrokes. For example, if you want to press Enter, send "\r" (NOT "\n"). +//! On Linux, use `sendkey -a` to see the actual value of a keystroke. The +//! [Wikipedia page](https://en.wikipedia.org/wiki/ANSI_escape_code) also +//! contains a list of common ANSI escape codes. +//! +//! Step 5: Wait for Nushell to respond and update the terminal state. Sometimes +//! Nushell will emit terminal events (e.g. querying cursor position, modifying +//! system clipboard), and these events need to be handled. `read_to_end()` will +//! do that for you, and you can use `pty_write_handler()` for the event handler +//! if you don't care about any of the terminal events. +//! +//! Step 6: Examine the terminal state and make assertions. You can use +//! `extract_text()` and `extract_cursor()` if you only care about the text. use alacritty_terminal::{ event::{Event, EventListener, WindowSize}, From a108d788de052c40442871f55084415bb4cccc04 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sun, 2 Jun 2024 19:25:03 +0800 Subject: [PATCH 05/15] Fix typo --- tests/terminal/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index 457c1ebd1e..bc375a77dc 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -18,7 +18,7 @@ fn auto_cd_works() { ); let (mut term, mut events) = default_terminal(); - // Wait for Nushell to initalize. + // Wait for Nushell to initialize. std::thread::sleep(Duration::from_millis(500)); #[cfg(windows)] @@ -60,7 +60,7 @@ fn command_hints_are_pwd_aware() { ); let (mut term, mut events) = default_terminal(); - // Wait for Nushell to initalize. + // Wait for Nushell to initialize. std::thread::sleep(Duration::from_millis(500)); pty.writer().write_all(b"cd foo\r").unwrap(); From 8babd9bb1cce5ecf34b2b3f62e0b21e9a20e8da1 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sun, 2 Jun 2024 19:28:37 +0800 Subject: [PATCH 06/15] Fix clippy --- crates/nu-test-support/src/terminal.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs index 13ab86a6dc..ee0bed2421 100644 --- a/crates/nu-test-support/src/terminal.rs +++ b/crates/nu-test-support/src/terminal.rs @@ -87,7 +87,7 @@ pub fn pty_with_nushell(args: Vec, pwd: Option) -> Pty { cell_width: 0, cell_height: 0, }; - tty::new(&options, window_size, 0).unwrap() + tty::new(&options, window_size, 0).expect("creating a PTY succeeds") } /// Reads from `pty` until no more data is available. Will periodically call @@ -138,14 +138,16 @@ pub fn read_to_end( /// minimum amount of event handling you need to get Nushell working. pub fn pty_write_handler(_terminal: &mut Term, pty: &mut Pty, event: Event) { if let Event::PtyWrite(text) = event { - pty.writer().write_all(text.as_bytes()).unwrap(); + pty.writer() + .write_all(text.as_bytes()) + .expect("writing to PTY succeeds"); } } /// Extracts the current cursor position. pub fn extract_cursor(terminal: &Term) -> (usize, usize) { let cursor = terminal.grid().cursor.point; - (cursor.line.0 as usize, cursor.column.0 as usize) + (cursor.line.0 as usize, cursor.column.0) } /// Extracts all visible text, ignoring text styles. @@ -155,7 +157,9 @@ pub fn extract_text(terminal: &Term) -> Vec { if point.column == 0 { text.push(String::new()); } - text.last_mut().unwrap().push(cell.c); + text.last_mut() + .expect("terminal grid start at column 0") + .push(cell.c); } text } From 3263f53b69d5cf1845bfa79f426547c2a1a10e74 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 3 Jun 2024 05:36:09 +0800 Subject: [PATCH 07/15] Disable the tests on MacOS --- tests/terminal/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index bc375a77dc..2d3d4d0ba3 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -6,6 +6,7 @@ use nu_test_support::terminal::{ use std::{io::Write, time::Duration}; #[test] +#[cfg(any(windows, linux))] fn auto_cd_works() { // Setup a directory with a sub-directory in it. let cwd = tempfile::tempdir().unwrap(); @@ -38,6 +39,7 @@ fn auto_cd_works() { } #[test] +#[cfg(any(windows, linux))] fn command_hints_are_pwd_aware() { // PWD-aware command hints require setting history file format to "sqlite". let nu_config = tempfile::NamedTempFile::new().unwrap(); From ef0472aebb5e1756dd79a23716ddeae79b3e1bc3 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 3 Jun 2024 06:18:33 +0800 Subject: [PATCH 08/15] Disable the module instead --- tests/main.rs | 1 + tests/terminal/mod.rs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/main.rs b/tests/main.rs index dddf851151..64a171a299 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -14,4 +14,5 @@ mod plugins; mod repl; mod scope; mod shell; +#[cfg(not(macos))] mod terminal; diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index 2d3d4d0ba3..bc375a77dc 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -6,7 +6,6 @@ use nu_test_support::terminal::{ use std::{io::Write, time::Duration}; #[test] -#[cfg(any(windows, linux))] fn auto_cd_works() { // Setup a directory with a sub-directory in it. let cwd = tempfile::tempdir().unwrap(); @@ -39,7 +38,6 @@ fn auto_cd_works() { } #[test] -#[cfg(any(windows, linux))] fn command_hints_are_pwd_aware() { // PWD-aware command hints require setting history file format to "sqlite". let nu_config = tempfile::NamedTempFile::new().unwrap(); From 2d3c55b2ec11c3d86a6f2450167b35dfa2b81f4e Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 3 Jun 2024 06:43:37 +0800 Subject: [PATCH 09/15] Take 2 --- tests/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main.rs b/tests/main.rs index 64a171a299..5821d754ad 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -14,5 +14,5 @@ mod plugins; mod repl; mod scope; mod shell; -#[cfg(not(macos))] +#[cfg(not(target_os = "macos"))] mod terminal; From 68be6cd0e6c03c876b93b3b45d049595c85721b3 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sun, 2 Jun 2024 23:22:33 +0000 Subject: [PATCH 10/15] Trying to debug on the CI --- tests/terminal/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index bc375a77dc..1cbb243c40 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -77,5 +77,8 @@ fn command_hints_are_pwd_aware() { // Examine the terminal state. let (row, _col) = extract_cursor(&term); let text = extract_text(&term); + for line in &text { + println!("{}", line); + } assert!(text[row - 2].contains("print 'FOO'")); } From 4bb195e36fee82820d8f64eaa5c6b8bb57402bab Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sun, 2 Jun 2024 23:35:06 +0000 Subject: [PATCH 11/15] Set "--no-config-file" --- crates/nu-test-support/src/terminal.rs | 7 +++++-- tests/terminal/mod.rs | 7 ++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs index ee0bed2421..c81100290b 100644 --- a/crates/nu-test-support/src/terminal.rs +++ b/crates/nu-test-support/src/terminal.rs @@ -73,10 +73,13 @@ pub fn default_terminal() -> (Term, mpsc::Receiver) { /// Creates a PTY and connect the slave end to a Nushell process. If `pwd` is /// None, the Nushell process will inherit PWD from the current process. -pub fn pty_with_nushell(args: Vec, pwd: Option) -> Pty { +pub fn pty_with_nushell(args: Vec<&str>, pwd: Option) -> Pty { let executable = crate::fs::executable_path().to_string_lossy().to_string(); let options = Options { - shell: Some(Shell::new(executable, args)), + shell: Some(Shell::new( + executable, + args.iter().map(|s| s.to_string()).collect(), + )), working_directory: pwd, hold: false, env: HashMap::new(), diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index 1cbb243c40..67c71f4a89 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -12,10 +12,7 @@ fn auto_cd_works() { std::fs::create_dir(cwd.path().join("foo")).unwrap(); // Create the PTY and the terminal. - let mut pty = pty_with_nushell( - vec!["--no-config-file".to_string()], - Some(cwd.path().to_path_buf()), - ); + let mut pty = pty_with_nushell(vec!["--no-config-file"], Some(cwd.path().to_path_buf())); let (mut term, mut events) = default_terminal(); // Wait for Nushell to initialize. @@ -55,7 +52,7 @@ fn command_hints_are_pwd_aware() { // Create the PTY and the terminal. let mut pty = pty_with_nushell( - vec!["--config".to_string(), nu_config_string], + vec!["--no-config-file", "--config", &nu_config_string], Some(cwd.path().to_path_buf()), ); let (mut term, mut events) = default_terminal(); From 3b59407cfb0a4783f6c9b4f8fdea402f9efb10c9 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Sun, 2 Jun 2024 23:39:22 +0000 Subject: [PATCH 12/15] Specify both "--config" and "--env-config" --- tests/terminal/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index 67c71f4a89..75f3cd6d9c 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -52,7 +52,12 @@ fn command_hints_are_pwd_aware() { // Create the PTY and the terminal. let mut pty = pty_with_nushell( - vec!["--no-config-file", "--config", &nu_config_string], + vec![ + "--config", + &nu_config_string, + "--env-config", + &nu_config_string, + ], Some(cwd.path().to_path_buf()), ); let (mut term, mut events) = default_terminal(); From 5ae2665bb44a4e093845bf4747913b5ea6b88b86 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 3 Jun 2024 07:49:20 +0800 Subject: [PATCH 13/15] Re-enable test on MacOS --- crates/nu-test-support/src/terminal.rs | 6 ++++-- tests/main.rs | 1 - tests/terminal/mod.rs | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs index c81100290b..97a051a5ea 100644 --- a/crates/nu-test-support/src/terminal.rs +++ b/crates/nu-test-support/src/terminal.rs @@ -10,8 +10,10 @@ //! will create one for you with sensible defaults. //! //! Step 2: Create a PTY and spawn a Nushell process to the slave end. -//! `pty_with_nushell()` will do that for you. It's probably a good idea to pass -//! `--no-config-file` as argument to Nushell. +//! `pty_with_nushell()` will do that for you. Here you can set PWD or pass +//! command line arguments to Nushell. It's always a good idea to pass +//! `--no-config-file`, otherwise Nushell will ask if you want to create one +//! with default, and that messes up the input. //! //! Step 3: Wait for Nushell to initialize (sleeping for 500ms should do). On //! Linux, trying to write to the PTY before Nushell finishes initialization diff --git a/tests/main.rs b/tests/main.rs index 5821d754ad..dddf851151 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -14,5 +14,4 @@ mod plugins; mod repl; mod scope; mod shell; -#[cfg(not(target_os = "macos"))] mod terminal; diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index 75f3cd6d9c..72f0a5212a 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -79,8 +79,5 @@ fn command_hints_are_pwd_aware() { // Examine the terminal state. let (row, _col) = extract_cursor(&term); let text = extract_text(&term); - for line in &text { - println!("{}", line); - } assert!(text[row - 2].contains("print 'FOO'")); } From 9bb7cf4fd49b49c3b85dbcbe6cc11a262006f52f Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Wed, 5 Jun 2024 06:21:48 +0800 Subject: [PATCH 14/15] Update doc --- crates/nu-test-support/src/terminal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs index 97a051a5ea..6b2349d075 100644 --- a/crates/nu-test-support/src/terminal.rs +++ b/crates/nu-test-support/src/terminal.rs @@ -24,7 +24,7 @@ //! Step 4: Write data to the PTY. Any data you sent will appear to Nushell as //! if they were typed in a terminal. ANSI escape codes are used for special //! keystrokes. For example, if you want to press Enter, send "\r" (NOT "\n"). -//! On Linux, use `sendkey -a` to see the actual value of a keystroke. The +//! On Linux, use `showkey -a` to see the actual value of a keystroke. The //! [Wikipedia page](https://en.wikipedia.org/wiki/ANSI_escape_code) also //! contains a list of common ANSI escape codes. //! From cafbace836d773128fb9530b139334191eee370f Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 8 Jul 2024 00:41:35 +0800 Subject: [PATCH 15/15] Put sleeping 500ms into pty_with_nushell() --- crates/nu-test-support/src/terminal.rs | 22 ++++++++++++++-------- tests/terminal/mod.rs | 8 +------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/nu-test-support/src/terminal.rs b/crates/nu-test-support/src/terminal.rs index 6b2349d075..e3cf76aa40 100644 --- a/crates/nu-test-support/src/terminal.rs +++ b/crates/nu-test-support/src/terminal.rs @@ -15,11 +15,11 @@ //! `--no-config-file`, otherwise Nushell will ask if you want to create one //! with default, and that messes up the input. //! -//! Step 3: Wait for Nushell to initialize (sleeping for 500ms should do). On -//! Linux, trying to write to the PTY before Nushell finishes initialization -//! appears to succeed, but the data will be lost. I'm not sure if this is a bug -//! or just weird behavior of Linux PTY. It's not necessary on Windows, but it -//! won't hurt either. +//! Step 3: Wait for Nushell to initialize (sleeping for 500ms should do). +//! `pty_with_nushell()` also does that for you. On Linux, trying to write to +//! the PTY before Nushell finishes initialization appears to succeed, but the +//! data will be lost. I'm not sure if this is a bug or just weird behavior of +//! Linux PTY. It's not necessary on Windows, but it won't hurt either. //! //! Step 4: Write data to the PTY. Any data you sent will appear to Nushell as //! if they were typed in a terminal. ANSI escape codes are used for special @@ -73,8 +73,9 @@ pub fn default_terminal() -> (Term, mpsc::Receiver) { (Term::new(config, &size, EventProxy(tx)), rx) } -/// Creates a PTY and connect the slave end to a Nushell process. If `pwd` is -/// None, the Nushell process will inherit PWD from the current process. +/// Creates a PTY and connect the slave end to a Nushell process, then wait for +/// Nushell to initialize. If `pwd` is None, the Nushell process will inherit +/// PWD from the current process. pub fn pty_with_nushell(args: Vec<&str>, pwd: Option) -> Pty { let executable = crate::fs::executable_path().to_string_lossy().to_string(); let options = Options { @@ -92,7 +93,12 @@ pub fn pty_with_nushell(args: Vec<&str>, pwd: Option) -> Pty { cell_width: 0, cell_height: 0, }; - tty::new(&options, window_size, 0).expect("creating a PTY succeeds") + let pty = tty::new(&options, window_size, 0).expect("creating a PTY should succeed"); + + // Wait for Nushell to initialize. + std::thread::sleep(Duration::from_millis(500)); + + pty } /// Reads from `pty` until no more data is available. Will periodically call diff --git a/tests/terminal/mod.rs b/tests/terminal/mod.rs index 72f0a5212a..5f7da7f621 100644 --- a/tests/terminal/mod.rs +++ b/tests/terminal/mod.rs @@ -3,7 +3,7 @@ use nu_test_support::terminal::{ default_terminal, extract_cursor, extract_text, pty_with_nushell, pty_write_handler, read_to_end, }; -use std::{io::Write, time::Duration}; +use std::io::Write; #[test] fn auto_cd_works() { @@ -15,9 +15,6 @@ fn auto_cd_works() { let mut pty = pty_with_nushell(vec!["--no-config-file"], Some(cwd.path().to_path_buf())); let (mut term, mut events) = default_terminal(); - // Wait for Nushell to initialize. - std::thread::sleep(Duration::from_millis(500)); - #[cfg(windows)] pty.writer().write_all(b".\\foo\r").unwrap(); #[cfg(unix)] @@ -62,9 +59,6 @@ fn command_hints_are_pwd_aware() { ); let (mut term, mut events) = default_terminal(); - // Wait for Nushell to initialize. - std::thread::sleep(Duration::from_millis(500)); - pty.writer().write_all(b"cd foo\r").unwrap(); pty.writer().write_all(b"print 'FOO'\r").unwrap(); pty.writer().write_all(b"cd ../bar\r").unwrap();