diff --git a/src/xatu/BUILD b/src/xatu/BUILD index dea31a6..dda8da0 100644 --- a/src/xatu/BUILD +++ b/src/xatu/BUILD @@ -41,6 +41,17 @@ rust_test( ], ) +sh_test( + name = "xatu_cli_test", + srcs = ["xatu_cli_test.sh"], + data = [ + ":xatu", + "testdata/clean.nix", + "testdata/invalid.nix", + "testdata/lint_findings.nix", + ], +) + rust_binary( name = "xatu", crate_name = "xatu_cli", diff --git a/src/xatu/xatu_cli_test.sh b/src/xatu/xatu_cli_test.sh new file mode 100755 index 0000000..c6763d9 --- /dev/null +++ b/src/xatu/xatu_cli_test.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +xatu="$TEST_SRCDIR/$TEST_WORKSPACE/src/xatu/xatu" +fixture="$TEST_SRCDIR/$TEST_WORKSPACE/src/xatu/testdata" +tmp="${TEST_TMPDIR:-$(mktemp -d)}" + +run_xatu() { + local name="$1" + local expected_status="$2" + shift 2 + + set +e + "$xatu" "$@" >"$tmp/$name.stdout" 2>"$tmp/$name.stderr" + local actual_status="$?" + set -e + + if [[ "$actual_status" != "$expected_status" ]]; then + echo "expected $name to exit $expected_status, got $actual_status" >&2 + echo "stdout:" >&2 + cat "$tmp/$name.stdout" >&2 + echo "stderr:" >&2 + cat "$tmp/$name.stderr" >&2 + exit 1 + fi +} + +cp "$fixture/clean.nix" "$tmp/clean.nix" +cp "$fixture/invalid.nix" "$tmp/invalid.nix" +cp "$fixture/lint_findings.nix" "$tmp/lint_findings.nix" +mkdir -p "$tmp/project/testdata" +cp "$fixture/invalid.nix" "$tmp/project/testdata/invalid.nix" + +run_xatu clean 0 "$tmp/clean.nix" +run_xatu findings 1 "$tmp/lint_findings.nix" +run_xatu fatal 2 "$tmp/invalid.nix" +run_xatu ignores_testdata_by_default 0 "$tmp/project" +run_xatu checks_explicit_testdata_path 2 "$tmp/project/testdata/invalid.nix" diff --git a/src/xatu/xatu_main.rs b/src/xatu/xatu_main.rs index 5c50fe8..d7e4474 100644 --- a/src/xatu/xatu_main.rs +++ b/src/xatu/xatu_main.rs @@ -1,7 +1,8 @@ use clap::Parser; use status::ResultExt; use std::path::{Path, PathBuf}; -use xatu::SourceFile; +use std::process::ExitCode; +use xatu::{Report, SourceFile}; fn is_nix_file(path: &Path) -> bool { path.extension().is_some_and(|extension| extension == "nix") @@ -28,11 +29,18 @@ fn is_excluded_dir(path: &Path) -> bool { } fn collect_source_files(path: &Path) -> status::Result> { + collect_source_files_with_exclusions(path, false) +} + +fn collect_source_files_with_exclusions( + path: &Path, + exclude_current_dir: bool, +) -> status::Result> { let metadata = std::fs::symlink_metadata(path) .with_context(|| format!("failed to read metadata for {}", path.display()))?; if metadata.file_type().is_dir() { - if is_excluded_dir(path) { + if exclude_current_dir && is_excluded_dir(path) { return Ok(Vec::new()); } @@ -43,7 +51,7 @@ fn collect_source_files(path: &Path) -> status::Result> { { let entry = entry.with_context(|| format!("failed to read entry in {}", path.display()))?; - files.extend(collect_source_files(&entry.path())?); + files.extend(collect_source_files_with_exclusions(&entry.path(), true)?); } return Ok(files); @@ -66,9 +74,7 @@ struct Args { paths: Vec, } -fn main() -> status::Result<()> { - let args = Args::parse(); - +fn analyze(args: Args) -> status::Result { let paths: Vec = args .paths .into_iter() @@ -83,8 +89,22 @@ fn main() -> status::Result<()> { .flatten() .collect(); - let report = xatu::future_sight(files)?; - print!("{report}"); - - Ok(()) + xatu::future_sight(files) +} + +fn main() -> ExitCode { + match analyze(Args::parse()) { + Ok(report) if report.is_clean() => { + println!("No findings."); + ExitCode::SUCCESS + } + Ok(report) => { + print!("{report}"); + ExitCode::from(1) + } + Err(error) => { + eprintln!("{error}"); + ExitCode::from(2) + } + } }