diff --git a/Cargo.lock b/Cargo.lock index 4664e96..fcd50d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,7 +179,7 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "codeowners" -version = "0.2.11" +version = "0.2.13" dependencies = [ "assert_cmd", "clap", @@ -342,9 +342,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "float-cmp" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -615,9 +615,9 @@ checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "predicates" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", diff --git a/Cargo.toml b/Cargo.toml index e7b30f0..b10c62a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codeowners" -version = "0.2.11" +version = "0.2.13" edition = "2024" [profile.release] @@ -34,6 +34,6 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [dev-dependencies] assert_cmd = "2.0.16" rusty-hook = "^0.11.2" -predicates = "3.1.2" +predicates = "3.1.3" pretty_assertions = "1.4.1" # Shows a more readable diff when comparing objects indoc = "2.0.5" diff --git a/src/config.rs b/src/config.rs index f09ce2b..aa087df 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,9 +24,6 @@ pub struct Config { #[serde(default = "default_ignore_dirs")] pub ignore_dirs: Vec, - - #[serde(default = "default_skip_untracked_files")] - pub skip_untracked_files: bool, } #[allow(dead_code)] @@ -63,10 +60,6 @@ fn vendored_gems_path() -> String { "vendored/".to_string() } -fn default_skip_untracked_files() -> bool { - true -} - fn default_ignore_dirs() -> Vec { vec![ ".cursor".to_owned(), diff --git a/src/files.rs b/src/files.rs deleted file mode 100644 index 93246b8..0000000 --- a/src/files.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - process::Command, -}; - -use core::fmt; -use error_stack::{Context, Result, ResultExt}; - -#[derive(Debug)] -pub enum Error { - Io, -} - -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Io => fmt.write_str("Error::Io"), - } - } -} - -impl Context for Error {} - -pub(crate) fn untracked_files(base_path: &Path) -> Result, Error> { - let output = Command::new("git") - .args(["ls-files", "--others", "--exclude-standard", "--full-name", "-z", "--", "."]) - .current_dir(base_path) - .output() - .change_context(Error::Io)?; - - if !output.status.success() { - return Ok(Vec::new()); - } - - let results: Vec = output - .stdout - .split(|&b| b == b'\0') - .filter(|chunk| !chunk.is_empty()) - .map(|rel| std::str::from_utf8(rel).change_context(Error::Io).map(|s| base_path.join(s))) - .collect::>()?; - - Ok(results) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_untracked_files() { - let tmp_dir = tempfile::tempdir().unwrap(); - let untracked = untracked_files(tmp_dir.path()).unwrap(); - assert!(untracked.is_empty()); - - std::process::Command::new("git") - .arg("init") - .current_dir(tmp_dir.path()) - .output() - .expect("failed to run git init"); - - std::fs::write(tmp_dir.path().join("test.txt"), "test").unwrap(); - let untracked = untracked_files(tmp_dir.path()).unwrap(); - assert!(untracked.len() == 1); - let expected = tmp_dir.path().join("test.txt"); - assert!(untracked[0] == expected); - } - - #[test] - fn test_untracked_files_with_spaces_and_parens() { - let tmp_dir = tempfile::tempdir().unwrap(); - - std::process::Command::new("git") - .arg("init") - .current_dir(tmp_dir.path()) - .output() - .expect("failed to run git init"); - - // Nested dirs with spaces and parentheses - let d1 = tmp_dir.path().join("dir with spaces"); - let d2 = d1.join("(special)"); - std::fs::create_dir_all(&d2).unwrap(); - - let f1 = d1.join("file (1).txt"); - let f2 = d2.join("a b (2).rb"); - std::fs::write(&f1, "one").unwrap(); - std::fs::write(&f2, "two").unwrap(); - - let mut untracked = untracked_files(tmp_dir.path()).unwrap(); - untracked.sort(); - - let mut expected = vec![f1, f2]; - expected.sort(); - - assert_eq!(untracked, expected); - } - - #[test] - fn test_untracked_files_multiple_files_order_insensitive() { - let tmp_dir = tempfile::tempdir().unwrap(); - - std::process::Command::new("git") - .arg("init") - .current_dir(tmp_dir.path()) - .output() - .expect("failed to run git init"); - - let f1 = tmp_dir.path().join("a.txt"); - let f2 = tmp_dir.path().join("b.txt"); - let f3 = tmp_dir.path().join("c.txt"); - std::fs::write(&f1, "A").unwrap(); - std::fs::write(&f2, "B").unwrap(); - std::fs::write(&f3, "C").unwrap(); - - let mut untracked = untracked_files(tmp_dir.path()).unwrap(); - untracked.sort(); - - let mut expected = vec![f1, f2, f3]; - expected.sort(); - - assert_eq!(untracked, expected); - } - - #[test] - fn test_untracked_files_excludes_staged() { - let tmp_dir = tempfile::tempdir().unwrap(); - - std::process::Command::new("git") - .arg("init") - .current_dir(tmp_dir.path()) - .output() - .expect("failed to run git init"); - - let staged = tmp_dir.path().join("staged.txt"); - let unstaged = tmp_dir.path().join("unstaged.txt"); - std::fs::write(&staged, "I will be staged").unwrap(); - std::fs::write(&unstaged, "I remain untracked").unwrap(); - - // Stage one file - let add_status = std::process::Command::new("git") - .arg("add") - .arg("staged.txt") - .current_dir(tmp_dir.path()) - .output() - .expect("failed to run git add"); - assert!( - add_status.status.success(), - "git add failed: {}", - String::from_utf8_lossy(&add_status.stderr) - ); - - let mut untracked = untracked_files(tmp_dir.path()).unwrap(); - untracked.sort(); - - let expected = vec![unstaged]; - assert_eq!(untracked, expected); - } -} diff --git a/src/lib.rs b/src/lib.rs index d286275..e5e2cec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,9 @@ pub mod cache; pub(crate) mod common_test; pub mod config; pub mod crosscheck; -pub(crate) mod files; pub mod ownership; pub(crate) mod project; pub mod project_builder; pub mod project_file_builder; pub mod runner; +pub(crate) mod tracked_files; diff --git a/src/ownership/for_file_fast.rs b/src/ownership/for_file_fast.rs index c9971cb..c5fe908 100644 --- a/src/ownership/for_file_fast.rs +++ b/src/ownership/for_file_fast.rs @@ -303,7 +303,6 @@ mod tests { vendored_gems_path: vendored_path.to_string(), cache_directory: "tmp/cache/codeowners".to_string(), ignore_dirs: vec![], - skip_untracked_files: false, } } diff --git a/src/project_builder.rs b/src/project_builder.rs index 65af254..45a9d88 100644 --- a/src/project_builder.rs +++ b/src/project_builder.rs @@ -13,9 +13,9 @@ use tracing::{instrument, warn}; use crate::{ cache::Cache, config::Config, - files, project::{DirectoryCodeownersFile, Error, Package, PackageType, Project, ProjectFile, Team, VendoredGem, deserializers}, project_file_builder::ProjectFileBuilder, + tracked_files, }; type AbsolutePath = PathBuf; @@ -61,16 +61,16 @@ impl<'a> ProjectBuilder<'a> { // Prune traversal early: skip heavy and irrelevant directories let ignore_dirs = self.config.ignore_dirs.clone(); let base_path = self.base_path.clone(); - let untracked_files = if self.config.skip_untracked_files { - files::untracked_files(&base_path).unwrap_or_default() - } else { - vec![] - }; + let tracked_files = tracked_files::find_tracked_files(&self.base_path); builder.filter_entry(move |entry: &DirEntry| { let path = entry.path(); let file_name = entry.file_name().to_str().unwrap_or(""); - if !untracked_files.is_empty() && untracked_files.contains(&path.to_path_buf()) { + if let Some(tracked_files) = &tracked_files + && let Some(ft) = entry.file_type() + && ft.is_file() + && !tracked_files.contains_key(path) + { return false; } if let Some(ft) = entry.file_type() diff --git a/src/tracked_files.rs b/src/tracked_files.rs new file mode 100644 index 0000000..cbbcba9 --- /dev/null +++ b/src/tracked_files.rs @@ -0,0 +1,58 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + process::Command, +}; + +pub(crate) fn find_tracked_files(base_path: &Path) -> Option> { + let output = Command::new("git") + .args(["ls-files", "--full-name", "-z", "--", "."]) + .current_dir(base_path) + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let results: HashMap = output + .stdout + .split(|&b| b == b'\0') + .filter(|chunk| !chunk.is_empty()) + .map(|rel| std::str::from_utf8(rel).ok().map(|s| (base_path.join(s), true))) + .collect::>>()?; + + Some(results) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_untracked_files() { + let tmp_dir = tempfile::tempdir().unwrap(); + assert!(find_tracked_files(tmp_dir.path()).is_none()); + + std::process::Command::new("git") + .arg("init") + .current_dir(tmp_dir.path()) + .output() + .expect("failed to run git init"); + + std::fs::write(tmp_dir.path().join("test.txt"), "test").unwrap(); + let tracked = find_tracked_files(tmp_dir.path()).unwrap(); + assert!(tracked.is_empty()); + + std::process::Command::new("git") + .arg("add") + .arg("test.txt") + .current_dir(tmp_dir.path()) + .output() + .expect("failed to add test.txt"); + + let tracked = find_tracked_files(tmp_dir.path()).unwrap(); + assert!(tracked.len() == 1); + assert!(tracked.get(&tmp_dir.path().join("test.txt")).unwrap()); + } +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 780026b..226e528 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,10 +1,47 @@ +use assert_cmd::prelude::*; +use codeowners::runner::{self, RunConfig}; +use predicates::prelude::*; use std::fs; use std::path::Path; -use std::process::Command; - -use codeowners::runner::{self, RunConfig}; +use std::{error::Error, process::Command}; use tempfile::TempDir; +#[allow(dead_code)] +pub enum OutputStream { + Stdout, + Stderr, +} + +#[allow(dead_code)] +pub fn run_codeowners( + relative_fixture_path: &str, + command: &[&str], + success: bool, + stream: OutputStream, + output_predicate: I, +) -> Result<(), Box> +where + I: assert_cmd::assert::IntoOutputPredicate

, + P: Predicate<[u8]>, +{ + let fixture_root = Path::new("tests/fixtures").join(relative_fixture_path); + let temp_dir = setup_fixture_repo(&fixture_root); + let project_root = temp_dir.path(); + git_add_all_files(project_root); + let mut cmd = Command::cargo_bin("codeowners")?; + let assert = cmd.arg("--project-root").arg(project_root).arg("--no-cache").args(command).assert(); + let assert = if success { assert.success() } else { assert.failure() }; + match stream { + OutputStream::Stdout => { + assert.stdout(output_predicate); + } + OutputStream::Stderr => { + assert.stderr(output_predicate); + } + } + Ok(()) +} + #[allow(dead_code)] pub fn teardown() { glob::glob("tests/fixtures/*/tmp/cache/codeowners") @@ -36,6 +73,35 @@ pub fn copy_dir_recursive(from: &Path, to: &Path) { } } +#[allow(dead_code)] +pub fn git_reset_all(path: &Path) { + let status = Command::new("git") + .arg("reset") + .current_dir(path) + .output() + .expect("failed to run git reset --all"); + assert!( + status.status.success(), + "git reset --all failed: {}", + String::from_utf8_lossy(&status.stderr) + ); +} + +#[allow(dead_code)] +pub fn git_add_all_files(path: &Path) { + let status = Command::new("git") + .arg("add") + .arg("--all") + .current_dir(path) + .output() + .expect("failed to run git add --all"); + assert!( + status.status.success(), + "git add --all failed: {}", + String::from_utf8_lossy(&status.stderr) + ); +} + #[allow(dead_code)] pub fn init_git_repo(path: &Path) { let status = Command::new("git") diff --git a/tests/crosscheck_owners_test.rs b/tests/crosscheck_owners_test.rs index c9252a3..2baf5cd 100644 --- a/tests/crosscheck_owners_test.rs +++ b/tests/crosscheck_owners_test.rs @@ -6,6 +6,8 @@ use std::{error::Error, fs, path::Path, process::Command}; mod common; use common::setup_fixture_repo; +use crate::common::git_add_all_files; + const FIXTURE: &str = "tests/fixtures/valid_project"; #[test] @@ -22,6 +24,7 @@ fn test_crosscheck_owners_reports_team_mismatch() -> Result<(), Box> "/ruby/app/models/payroll.rb @PaymentsTeam", ); fs::write(&codeowners_path, modified)?; + git_add_all_files(project_root); // Act + Assert Command::cargo_bin("codeowners")? @@ -53,6 +56,7 @@ fn test_crosscheck_owners_reports_unowned_mismatch() -> Result<(), Box Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/invalid_project") - .arg("--no-cache") - .arg("validate") - .assert() - .failure() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "invalid_project", + &["validate"], + false, + OutputStream::Stdout, + predicate::eq(indoc! {" - CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file + CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file - Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways + Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways - gems/payroll_calculator/calculator.rb - owner: Payments - - Owner annotation at the top of the file - owner: Payroll - - Owner specified in Team YML's `owned_gems` + gems/payroll_calculator/calculator.rb + owner: Payments + - Owner annotation at the top of the file + owner: Payroll + - Owner specified in Team YML's `owned_gems` - ruby/app/services/multi_owned.rb - owner: Payments - - Owner annotation at the top of the file - owner: Payroll - - Owner specified in `ruby/app/services/.codeowner` + ruby/app/services/multi_owned.rb + owner: Payments + - Owner annotation at the top of the file + owner: Payroll + - Owner specified in `ruby/app/services/.codeowner` - Found invalid team annotations - - ruby/app/models/blockchain.rb is referencing an invalid team - 'Web3' + Found invalid team annotations + - ruby/app/models/blockchain.rb is referencing an invalid team - 'Web3' - Some files are missing ownership - - ruby/app/unowned.rb + Some files are missing ownership + - ruby/app/unowned.rb - "})); + "}), + )?; Ok(()) } #[test] fn test_for_file() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/invalid_project") - .arg("--no-cache") - .arg("for-file") - .arg("ruby/app/models/blockchain.rb") - .assert() - .success() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "invalid_project", + &["for-file", "ruby/app/models/blockchain.rb"], + true, + OutputStream::Stdout, + predicate::eq(indoc! {" Team: Unowned Github Team: Unowned Team YML: Description: - Unowned - "})); + "}), + )?; Ok(()) } #[test] fn test_for_file_multiple_owners() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/invalid_project") - .arg("--no-cache") - .arg("for-file") - .arg("ruby/app/services/multi_owned.rb") - .assert() - .failure() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "invalid_project", + &["for-file", "ruby/app/services/multi_owned.rb"], + false, + OutputStream::Stdout, + predicate::eq(indoc! {" Error: file is owned by multiple teams! Team: Payments @@ -84,6 +81,7 @@ fn test_for_file_multiple_owners() -> Result<(), Box> { Team YML: config/teams/payroll.yml Description: - Owner specified in `ruby/app/services/.codeowner` - "})); + "}), + )?; Ok(()) } diff --git a/tests/multiple_directory_owners_test.rs b/tests/multiple_directory_owners_test.rs index 30162b6..429a340 100644 --- a/tests/multiple_directory_owners_test.rs +++ b/tests/multiple_directory_owners_test.rs @@ -1,30 +1,36 @@ -use assert_cmd::prelude::*; -use std::{error::Error, path::Path, process::Command}; +use std::{error::Error, path::Path}; + +use common::OutputStream; +use common::run_codeowners; +use predicates::prelude::*; + +mod common; #[test] fn test_validate() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/multiple-directory-owners") - .arg("--no-cache") - .arg("validate") - .assert() - .success(); + run_codeowners( + "multiple-directory-owners", + &["validate"], + true, + OutputStream::Stdout, + predicate::eq(""), + )?; Ok(()) } #[test] fn test_generate() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/multiple-directory-owners") - .arg("--codeowners-file-path") - .arg("../../../tmp/CODEOWNERS") - .arg("--no-cache") - .arg("generate") - .assert() - .success(); + let codeowners_abs = std::env::current_dir()?.join("tmp/CODEOWNERS"); + let codeowners_str = codeowners_abs.to_str().unwrap(); + + run_codeowners( + "multiple-directory-owners", + &["--codeowners-file-path", codeowners_str, "generate"], + true, + OutputStream::Stdout, + predicate::eq(""), + )?; let expected_codeowners: String = std::fs::read_to_string(Path::new("tests/fixtures/multiple-directory-owners/.github/CODEOWNERS"))?; let actual_codeowners: String = std::fs::read_to_string(Path::new("tmp/CODEOWNERS"))?; diff --git a/tests/untracked_files_test.rs b/tests/untracked_files_test.rs index 61ca6db..ee03479 100644 --- a/tests/untracked_files_test.rs +++ b/tests/untracked_files_test.rs @@ -1,9 +1,11 @@ use assert_cmd::prelude::*; -use std::{error::Error, fs, path::Path, process::Command}; +use std::{error::Error, path::Path, process::Command}; mod common; use common::setup_fixture_repo; +use crate::common::{git_add_all_files, git_reset_all}; + const FIXTURE: &str = "tests/fixtures/invalid_project"; #[test] @@ -11,6 +13,7 @@ fn test_skip_untracked_files() -> Result<(), Box> { // Arrange: copy fixture to temp dir and change a single CODEOWNERS mapping let temp_dir = setup_fixture_repo(Path::new(FIXTURE)); let project_root = temp_dir.path(); + git_add_all_files(project_root); // Act + Assert Command::cargo_bin("codeowners")? @@ -21,14 +24,8 @@ fn test_skip_untracked_files() -> Result<(), Box> { .assert() .failure(); - // Add skip_untracked_false: false to project_root/config/code_ownership.yml - let config_path = project_root.join("config/code_ownership.yml"); - let original = fs::read_to_string(&config_path)?; - // Change payroll.rb ownership from @PayrollTeam to @PaymentsTeam to induce a mismatch - let modified = original.replace("skip_untracked_files: false", "skip_untracked_files: true"); - fs::write(&config_path, modified)?; - - // should succeed if skip_untracked_false is false + // should succeed if all files are untracked + git_reset_all(project_root); Command::cargo_bin("codeowners")? .arg("--project-root") .arg(project_root) diff --git a/tests/valid_project_test.rs b/tests/valid_project_test.rs index ebf6bad..0e28554 100644 --- a/tests/valid_project_test.rs +++ b/tests/valid_project_test.rs @@ -3,30 +3,31 @@ use indoc::indoc; use predicates::prelude::predicate; use std::{error::Error, fs, path::Path, process::Command}; +mod common; + +use common::OutputStream; +use common::run_codeowners; + #[test] fn test_validate() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project") - .arg("--no-cache") - .arg("validate") - .assert() - .success(); + run_codeowners("valid_project", &["validate"], true, OutputStream::Stdout, predicate::eq(""))?; Ok(()) } #[test] fn test_generate() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project") - .arg("--codeowners-file-path") - .arg("../../../tmp/CODEOWNERS") - .arg("--no-cache") - .arg("generate") - .assert() - .success(); + fs::create_dir_all("tmp")?; + let codeowners_abs = std::env::current_dir()?.join("tmp/CODEOWNERS"); + let codeowners_str = codeowners_abs.to_str().unwrap(); + + run_codeowners( + "valid_project", + &["--codeowners-file-path", codeowners_str, "generate"], + true, + OutputStream::Stdout, + predicate::eq(""), + )?; let expected_codeowners: String = std::fs::read_to_string(Path::new("tests/fixtures/valid_project/.github/CODEOWNERS"))?; let actual_codeowners: String = std::fs::read_to_string(Path::new("tmp/CODEOWNERS"))?; @@ -38,37 +39,35 @@ fn test_generate() -> Result<(), Box> { #[test] fn test_crosscheck_owners() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project") - .arg("--no-cache") - .arg("crosscheck-owners") - .assert() - .success() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "valid_project", + &["crosscheck-owners"], + true, + OutputStream::Stdout, + predicate::eq(indoc! {" Success! All files match between CODEOWNERS and for-file command. - "})); + "}), + )?; Ok(()) } #[test] fn test_for_file() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project") - .arg("--no-cache") - .arg("for-file") - .arg("ruby/app/models/payroll.rb") - .assert() - .success() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "valid_project", + &["for-file", "ruby/app/models/payroll.rb"], + true, + OutputStream::Stdout, + predicate::eq(indoc! {" Team: Payroll Github Team: @PayrollTeam Team YML: config/teams/payroll.yml Description: - Owner annotation at the top of the file - "})); + "}), + )?; + Ok(()) } @@ -244,15 +243,12 @@ fn test_for_file_with_2_ownerships() -> Result<(), Box> { #[test] fn test_for_team() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project") - .arg("--no-cache") - .arg("for-team") - .arg("Payroll") - .assert() - .success() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "valid_project", + &["for-team", "Payroll"], + true, + OutputStream::Stdout, + predicate::eq(indoc! {" # Code Ownership Report for `Payroll` Team ## Annotations at the top of file @@ -280,7 +276,9 @@ fn test_for_team() -> Result<(), Box> { ## Team owned gems /gems/payroll_calculator/**/** - "})); + "}), + )?; + Ok(()) } diff --git a/tests/valid_project_with_overrides_test.rs b/tests/valid_project_with_overrides_test.rs index 6d92fb4..3ced2da 100644 --- a/tests/valid_project_with_overrides_test.rs +++ b/tests/valid_project_with_overrides_test.rs @@ -1,18 +1,22 @@ -use assert_cmd::prelude::*; use indoc::indoc; use predicates::prelude::predicate; -use std::{error::Error, process::Command}; +use std::error::Error; + +mod common; + +use common::OutputStream; +use common::run_codeowners; #[test] #[ignore] fn test_validate() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project_with_overrides") - .arg("--no-cache") - .arg("validate") - .assert() - .success(); + run_codeowners( + "valid_project_with_overrides", + &["validate"], + true, + OutputStream::Stdout, + predicate::eq(""), + )?; Ok(()) } @@ -20,16 +24,15 @@ fn test_validate() -> Result<(), Box> { #[test] #[ignore] fn test_crosscheck_owners() -> Result<(), Box> { - Command::cargo_bin("codeowners")? - .arg("--project-root") - .arg("tests/fixtures/valid_project_with_overrides") - .arg("--no-cache") - .arg("crosscheck-owners") - .assert() - .success() - .stdout(predicate::eq(indoc! {" + run_codeowners( + "valid_project_with_overrides", + &["crosscheck-owners"], + true, + OutputStream::Stdout, + predicate::eq(indoc! {" Success! All files match between CODEOWNERS and for-file command. - "})); + "}), + )?; Ok(()) }