//! Integration tests for the `logshape` command. use std::fs; use std::path::PathBuf; use assert_cmd::Command; use predicates::prelude::*; use tempfile::tempdir; fn cargo_command() -> Command { Command::cargo_bin("logshape").expect("binary") } fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("..") .canonicalize() .expect("workspace root") } fn fixture_relative(path: &str) -> PathBuf { PathBuf::from("fixtures").join(path) } fn fixture(path: &str) -> PathBuf { workspace_root().join(fixture_relative(path)) } fn relative_to_workspace(path: &str) -> String { fixture_relative(path).display().to_string() } fn pwsh_command(script: impl AsRef) -> Command { let mut command = Command::new("pwsh"); command .arg("-NoProfile") .arg("-Command") .arg(script.as_ref()); command } #[test] fn groups_repetitive_logs_in_text_mode() { let mut command = cargo_command(); command .arg(fixture("logs/repetitive.log")) .assert() .success() .stdout(predicate::str::contains("count=2")) .stdout(predicate::str::contains( "connected to tcp://127.0.0.1:", )) .stdout(predicate::str::contains( "failed request id= path=", )); } #[test] fn emits_json_groups_with_limits() { let mut command = cargo_command(); command .arg("--json") .arg("--top") .arg("2") .arg("--min-count") .arg("2") .arg(fixture("logs/repetitive.log")) .assert() .success() .stdout(predicate::str::contains("\"count\":2")) .stdout(predicate::str::contains("\"pattern\"")) .stdout(predicate::str::contains("\"sample\"")) .stdout(predicate::str::contains("retry in").not()); } #[test] fn supports_powershell_pipeline_input() { let binary = assert_cmd::cargo::cargo_bin("logshape"); let input = relative_to_workspace("logs/repetitive.log"); let script = format!( "[System.IO.File]::ReadLines('{}') | & '{}' --json --top 1", input, binary.display() ); let mut command = pwsh_command(script); command .current_dir(workspace_root()) .assert() .success() .stdout(predicate::str::contains("\"count\":2")) .stdout(predicate::str::contains("\"first_line\":1")); } #[test] fn utf8_bom_stdin_groups_first_line_with_following_lines() { let mut command = cargo_command(); command .args(["--json", "--min-count", "2"]) .write_stdin( "\u{feff}INFO connected to tcp://127.0.0.1:5000\nINFO connected to tcp://127.0.0.1:5001\n", ) .assert() .success() .stdout(predicate::str::contains("\"count\":2")) .stdout(predicate::str::contains("\"first_line\":1")); } #[test] fn utf8_bom_log_file_groups_first_line_with_following_lines() { let root = tempdir().expect("tempdir"); let log_path = root.path().join("bom.log"); fs::write( &log_path, "\u{feff}INFO connected to tcp://127.0.0.1:5000\nINFO connected to tcp://127.0.0.1:5001\n", ) .expect("bom log"); let mut command = cargo_command(); command .arg("--json") .arg("--min-count") .arg("2") .arg(&log_path) .assert() .success() .stdout(predicate::str::contains("\"count\":2")) .stdout(predicate::str::contains( "connected to tcp://127.0.0.1:", )); } #[test] fn invalid_utf8_log_file_reports_read_error_instead_of_grouping() { let root = tempdir().expect("tempdir"); let log_path = root.path().join("invalid.log"); fs::write(&log_path, b"INFO ok\n\xff\xfe\xfd\n").expect("invalid log"); let mut command = cargo_command(); command .arg(&log_path) .assert() .failure() .stderr(predicate::str::contains("failed to read")) .stderr(predicate::str::contains("valid UTF-8")); } #[test] fn help_includes_grouping_examples() { let mut command = cargo_command(); command .arg("--help") .assert() .success() .stdout(predicate::str::contains("--min-count")) .stdout(predicate::str::contains("ConvertFrom-Json")) .stdout(predicate::str::contains( "logshape .\\fixtures\\logs\\repetitive.log", )); }