//! Integration tests for the `hitsnip` command. use std::fs; use std::path::{Path, PathBuf}; use std::str; use assert_cmd::Command; use predicates::prelude::*; use tempfile::{TempDir, tempdir}; const SAMPLE_RS: &str = "reading/sample.rs"; fn cargo_command() -> Command { Command::cargo_bin("hitsnip").expect("binary") } fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("..") .canonicalize() .expect("workspace root") } fn fixture(path: &str) -> PathBuf { workspace_root().join("fixtures").join(path) } fn temp_file(name: &str, contents: impl AsRef<[u8]>) -> (TempDir, PathBuf) { let dir = tempdir().expect("tempdir"); let path = dir.path().join(name); fs::write(&path, contents).expect("fixture"); (dir, path) } fn pwsh_command(script: impl AsRef) -> Command { let mut command = Command::new("pwsh"); command .arg("-NoProfile") .arg("-Command") .arg(script.as_ref()); command } fn ps_quote(value: impl std::fmt::Display) -> String { format!("'{}'", value.to_string().replace('\'', "''")) } fn relative_to_workspace(path: &Path) -> String { path.strip_prefix(workspace_root()) .expect("path within workspace") .display() .to_string() } #[test] fn merges_rg_hits_into_compact_text_snippets() { let mut command = cargo_command(); command .current_dir(workspace_root()) .arg("--context") .arg("1") .arg("--max-gap") .arg("1") .write_stdin(std::fs::read_to_string(fixture("hits/rg-output.txt")).expect("fixture")) .assert() .success() .stdout(predicate::str::contains( "path=fixtures\\reading\\sample.rs lines=17:27 hits=3", )) .stdout(predicate::str::contains("hit_lines=18,22,26")) .stdout(predicate::str::contains("18: pub fn run")) .stdout(predicate::str::contains( "path=fixtures\\reading\\sample.cs lines=8:10 hits=1", )); } #[test] fn accepts_explicit_hit_arguments_and_emits_json() { let sample = fixture(SAMPLE_RS); let hit_a = format!("{}:18", sample.display()); let hit_b = format!("{}:26:9", sample.display()); let mut command = cargo_command(); command .arg("--json") .arg("--context") .arg("0") .arg("--max-gap") .arg("10") .arg(&hit_a) .arg(&hit_b) .assert() .success() .stdout(predicate::str::contains("\"start_line\":18")) .stdout(predicate::str::contains("\"end_line\":26")) .stdout(predicate::str::contains("\"hit_count\":2")) .stdout(predicate::str::contains("\"hit_lines\":[18,26]")); } #[test] fn supports_powershell_pipeline_for_hit_lines() { let binary = assert_cmd::cargo::cargo_bin("hitsnip"); let sample = relative_to_workspace(&fixture(SAMPLE_RS)); let script = format!( "{},{} | & {} --context 0 --max-gap 0 --json", ps_quote(format!("{sample}:18")), ps_quote(format!("{sample}:26")), ps_quote(binary.display()) ); let mut command = pwsh_command(script); command .current_dir(workspace_root()) .assert() .success() .stdout(predicate::str::contains( "\"path\":\"fixtures\\\\reading\\\\sample.rs\"", )) .stdout(predicate::str::contains("\"selected\"").not()); } #[test] fn utf8_bom_source_file_does_not_pollute_first_snippet_line() { let (_dir, source) = temp_file("bom.rs", "\u{feff}fn main() {}\nfn helper() {}\n"); let mut command = cargo_command(); command .arg("--context") .arg("0") .arg(format!("{}:1", source.display())) .assert() .success() .stdout(predicate::str::contains("1: fn main() {}")) .stdout(predicate::str::contains("\u{feff}fn main").not()); } #[test] fn invalid_utf8_source_file_reports_read_error() { let (_dir, source) = temp_file("invalid-utf8.rs", [0x66, 0x80, 0x0A]); let mut command = cargo_command(); command .arg("--context") .arg("0") .arg(format!("{}:1", source.display())) .assert() .failure() .stderr(predicate::str::contains("failed to read")) .stderr(predicate::str::contains("invalid-utf8.rs")); } #[test] fn help_includes_rg_and_powershell_examples() { let mut command = cargo_command(); command .arg("--help") .assert() .success() .stdout(predicate::str::contains("rg -nH")) .stdout(predicate::str::contains("ConvertFrom-Json")) .stdout(predicate::str::contains("--max-gap")); } #[test] fn help_puts_rg_nh_before_pathless_rg_examples() { let mut command = cargo_command(); let output = command .arg("--help") .assert() .success() .get_output() .stdout .clone(); let help = str::from_utf8(&output).expect("help is utf8"); let rg_nh = help.find("rg -nH").expect("rg -nH example"); let rg_n_pathless = help .find("rg -n \"Mode::\"") .expect("pathless rg -n example"); assert!( rg_nh < rg_n_pathless, "the safest rg -nH example should appear before pathless rg -n" ); }