Skip to content

Commit a50023b

Browse files
bashandbonegoogle-labs-jules[bot]Copilot
authored
refactor(tests): make sparse checkout unit tests hermetic and complete (#52)
Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 29c57b3 commit a50023b

1 file changed

Lines changed: 159 additions & 1 deletion

File tree

src/git_manager.rs

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,27 @@ impl GitManager {
344344
})
345345
}
346346

347+
/// Creates a `GitManager` pointed at an explicit repository path.
348+
///
349+
/// Used in tests to avoid depending on the caller's working directory
350+
/// being a git repository.
351+
#[cfg(test)]
352+
fn with_repo_path(config_path: PathBuf, repo_path: &Path) -> Result<Self, SubmoduleError> {
353+
let git_ops = GitOpsManager::new(Some(repo_path), false)
354+
.map_err(|_| SubmoduleError::RepositoryError)?;
355+
356+
let config = Config::default()
357+
.load(&config_path, Config::default())
358+
.map_err(|e| SubmoduleError::ConfigError(format!("Failed to load config: {e}")))?;
359+
360+
Ok(Self {
361+
git_ops,
362+
config,
363+
config_path,
364+
verbose: false,
365+
})
366+
}
367+
347368
/// Check submodule repository status using gix APIs
348369
pub fn check_submodule_repository_status(
349370
&self,
@@ -406,7 +427,14 @@ impl GitManager {
406427
})
407428
}
408429

409-
/// Check sparse checkout configuration
430+
/// Check whether the sparse-checkout configuration for a submodule matches
431+
/// the expected paths.
432+
///
433+
/// Returns [`SparseStatus::Correct`] when every expected path is present in
434+
/// the configured file. Extra patterns in the file that are not in
435+
/// `expected_paths` are **not** treated as a mismatch; the check is a
436+
/// subset test (all expected ⊆ configured). Returns [`SparseStatus::Mismatch`]
437+
/// when at least one expected path is absent from the file.
410438
pub fn check_sparse_checkout_status(
411439
&self,
412440
submodule_path: &str,
@@ -1923,3 +1951,133 @@ impl GitManager {
19231951
Ok(())
19241952
}
19251953
}
1954+
1955+
#[cfg(test)]
1956+
mod tests {
1957+
use super::*;
1958+
use std::fs;
1959+
use tempfile::tempdir;
1960+
1961+
// Helper to create a `GitManager` for tests that need to call methods such as
1962+
// `check_sparse_checkout_status`. It initialises a real git repository in `repo_dir`
1963+
// via `git2` so that `GitOpsManager` can open it without depending on the caller's
1964+
// working directory being inside a git repo.
1965+
fn create_test_manager(repo_dir: &Path, config_path: PathBuf) -> GitManager {
1966+
git2::Repository::init(repo_dir).expect("Failed to init git repo");
1967+
fs::write(&config_path, "[defaults]\n").unwrap();
1968+
GitManager::with_repo_path(config_path, repo_dir).expect("Failed to create GitManager")
1969+
}
1970+
1971+
#[test]
1972+
fn test_sparse_checkout_not_configured() {
1973+
let temp_dir = tempdir().unwrap();
1974+
let submodule_path = temp_dir.path().join("submodule");
1975+
fs::create_dir(&submodule_path).unwrap();
1976+
1977+
// Create .git directory but NO sparse-checkout file
1978+
let git_dir = submodule_path.join(".git");
1979+
fs::create_dir(&git_dir).unwrap();
1980+
1981+
let manager = create_test_manager(temp_dir.path(), temp_dir.path().join("submod.toml"));
1982+
1983+
let expected_paths: Vec<String> = vec!["path/a".to_string()];
1984+
1985+
let status = manager
1986+
.check_sparse_checkout_status(
1987+
&submodule_path.to_string_lossy(),
1988+
&expected_paths,
1989+
)
1990+
.unwrap();
1991+
1992+
assert_eq!(status, SparseStatus::NotConfigured);
1993+
}
1994+
1995+
#[test]
1996+
fn test_sparse_checkout_correct() {
1997+
let temp_dir = tempdir().unwrap();
1998+
let submodule_path = temp_dir.path().join("submodule");
1999+
fs::create_dir(&submodule_path).unwrap();
2000+
2001+
// Create .git/info/sparse-checkout with the expected paths
2002+
let info_dir = submodule_path.join(".git").join("info");
2003+
fs::create_dir_all(&info_dir).unwrap();
2004+
let content = format!("{}\npath/a\npath/b\n", SPARSE_DENY_ALL);
2005+
fs::write(info_dir.join("sparse-checkout"), content).unwrap();
2006+
2007+
let manager = create_test_manager(temp_dir.path(), temp_dir.path().join("submod.toml"));
2008+
2009+
let expected_paths = vec!["path/a".to_string(), "path/b".to_string()];
2010+
2011+
let status = manager
2012+
.check_sparse_checkout_status(
2013+
&submodule_path.to_string_lossy(),
2014+
&expected_paths,
2015+
)
2016+
.unwrap();
2017+
2018+
assert_eq!(status, SparseStatus::Correct);
2019+
}
2020+
2021+
#[test]
2022+
fn test_sparse_checkout_correct_with_extras() {
2023+
// When the sparse-checkout file contains all expected paths plus additional
2024+
// ones, the result is still Correct (subset check, not equality).
2025+
let temp_dir = tempdir().unwrap();
2026+
let submodule_path = temp_dir.path().join("submodule");
2027+
fs::create_dir(&submodule_path).unwrap();
2028+
2029+
let info_dir = submodule_path.join(".git").join("info");
2030+
fs::create_dir_all(&info_dir).unwrap();
2031+
// File has path/a, path/b AND an extra path/c not in expected_paths
2032+
let content = format!("{}\npath/a\npath/b\npath/c\n", SPARSE_DENY_ALL);
2033+
fs::write(info_dir.join("sparse-checkout"), content).unwrap();
2034+
2035+
let manager = create_test_manager(temp_dir.path(), temp_dir.path().join("submod.toml"));
2036+
2037+
let expected_paths = vec!["path/a".to_string(), "path/b".to_string()];
2038+
2039+
let status = manager
2040+
.check_sparse_checkout_status(
2041+
&submodule_path.to_string_lossy(),
2042+
&expected_paths,
2043+
)
2044+
.unwrap();
2045+
2046+
assert_eq!(status, SparseStatus::Correct);
2047+
}
2048+
2049+
#[test]
2050+
fn test_sparse_checkout_mismatch() {
2051+
let temp_dir = tempdir().unwrap();
2052+
let submodule_path = temp_dir.path().join("submodule");
2053+
fs::create_dir(&submodule_path).unwrap();
2054+
2055+
// sparse-checkout has only path/a; path/b is expected but absent
2056+
let info_dir = submodule_path.join(".git").join("info");
2057+
fs::create_dir_all(&info_dir).unwrap();
2058+
let content = format!("{}\npath/a\n", SPARSE_DENY_ALL);
2059+
fs::write(info_dir.join("sparse-checkout"), content).unwrap();
2060+
2061+
let manager = create_test_manager(temp_dir.path(), temp_dir.path().join("submod.toml"));
2062+
2063+
let expected_paths = vec![
2064+
"path/a".to_string(),
2065+
"path/b".to_string(), // expected but not configured
2066+
];
2067+
2068+
let status = manager
2069+
.check_sparse_checkout_status(
2070+
&submodule_path.to_string_lossy(),
2071+
&expected_paths,
2072+
)
2073+
.unwrap();
2074+
2075+
match status {
2076+
SparseStatus::Mismatch { expected, actual } => {
2077+
assert_eq!(expected, vec!["path/a".to_string(), "path/b".to_string()]);
2078+
assert_eq!(actual, vec!["path/a".to_string()]);
2079+
}
2080+
_ => panic!("Expected Mismatch, got {:?}", status),
2081+
}
2082+
}
2083+
}

0 commit comments

Comments
 (0)