diff --git a/Cargo.toml b/Cargo.toml index 11b3361..f703b9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,3 +96,4 @@ tokio = { version = "1.37", features = ["rt", "macros"] } assert2 = "0.3" pretty_assertions = "1.4" rand = "0.8" +mockall = "0.12" diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 59345cb..c49172e 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -56,6 +56,7 @@ actix = { workspace = true } assert2 = { workspace = true } rand = { workspace = true } pretty_assertions = { workspace = true } +mockall = { workspace = true } [lints.clippy] nursery = { level = "warn", priority = -1 } diff --git a/crates/git/src/commit.rs b/crates/git/src/commit.rs index 39f4c6c..c7172db 100644 --- a/crates/git/src/commit.rs +++ b/crates/git/src/commit.rs @@ -1,7 +1,19 @@ +use config::newtype; +use derive_more::Display; // use git_next_config as config; -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] +#[derive( + Clone, + Debug, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + derive_more::Constructor, + derive_more::Display, +)] #[display("{}", sha)] pub struct Commit { sha: Sha, @@ -25,11 +37,8 @@ impl From for Commit { } } -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] -pub struct Sha(String); - -#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] -pub struct Message(String); +newtype!(Sha is a String, Display); +newtype!(Message is a String); #[derive(Clone, Debug)] pub struct Histories { diff --git a/crates/git/src/lib.rs b/crates/git/src/lib.rs index db926a5..d78f48f 100644 --- a/crates/git/src/lib.rs +++ b/crates/git/src/lib.rs @@ -22,4 +22,5 @@ pub use git_ref::GitRef; pub use git_remote::GitRemote; pub use repo_details::RepoDetails; pub use repository::OpenRepository; +pub use repository::OpenRepositoryLike; pub use repository::Repository; diff --git a/crates/git/src/repo_details.rs b/crates/git/src/repo_details.rs index f0d761d..d1538ce 100644 --- a/crates/git/src/repo_details.rs +++ b/crates/git/src/repo_details.rs @@ -57,4 +57,10 @@ impl RepoDetails { pub fn git_remote(&self) -> GitRemote { GitRemote::new(self.forge.hostname().clone(), self.repo_path.clone()) } + + pub fn with_hostname(mut self, hostname: git_next_config::Hostname) -> Self { + let forge = self.forge; + self.forge = forge.with_hostname(hostname); + self + } } diff --git a/crates/git/src/repository/mock.rs b/crates/git/src/repository/mock.rs index f50008d..c4b9513 100644 --- a/crates/git/src/repository/mock.rs +++ b/crates/git/src/repository/mock.rs @@ -10,29 +10,46 @@ use crate as git; use git_next_config as config; #[derive(Debug, Default, Clone)] -pub struct MockRepository { - open_repos: Arc>>, +pub struct FakeRepository { + open_repos: Arc>>, + clone_repos: Arc>>, } -impl MockRepository { +impl FakeRepository { pub fn new() -> Self { Self { open_repos: Default::default(), + clone_repos: Default::default(), } } pub fn given_can_be_opened( &mut self, - gitdir: &config::GitDir, - ) -> git::repository::MockOpenRepository { - let open_repo = git::repository::MockOpenRepository::new(); + repo_details: git::RepoDetails, + ) -> git::repository::FakeOpenRepository { + let gitdir = repo_details.gitdir.clone(); + let open_repo = git::repository::FakeOpenRepository::new(repo_details); #[allow(clippy::unwrap_used)] self.open_repos .lock() - .map(|mut or| or.insert(gitdir.clone(), open_repo.clone())) + .map(|mut or| or.insert(gitdir, open_repo.clone())) .unwrap(); open_repo } + pub fn given_can_be_cloned( + &mut self, + repo_details: git::RepoDetails, + ) -> git::repository::FakeOpenRepository { + let gitdir = repo_details.gitdir.clone(); + let clone_repo = git::repository::FakeOpenRepository::new(repo_details); + #[allow(clippy::unwrap_used)] + self.clone_repos + .lock() + .map(|mut or| or.insert(gitdir, clone_repo.clone())) + .unwrap(); + clone_repo + } + pub fn seal(self) -> (git::Repository, Self) { (git::Repository::Mock(self.clone()), self) } @@ -40,14 +57,14 @@ impl MockRepository { // drop repository to allow same mutable access to mock repository self } - pub fn get(&self, gitdir: &config::GitDir) -> Option { + pub fn get(&self, gitdir: &config::GitDir) -> Option { self.open_repos .lock() .map(|or| or.get(gitdir).cloned()) .unwrap_or(None) } } -impl git::repository::RepositoryLike for MockRepository { +impl git::repository::RepositoryLike for FakeRepository { fn open( &self, gitdir: &config::GitDir, @@ -59,13 +76,31 @@ impl git::repository::RepositoryLike for MockRepository { .map(|or| or.get(gitdir).cloned()) .transpose() .unwrap_or_else(|| Err(crate::repository::Error::InvalidGitDir(gitdir.clone()))) - .map(|mor| mor.into()) + .map(|mor| { + mor.log(format!("open gitdir:{gitdir}")); + mor.into() + }) } fn git_clone( &self, - _repo_details: &git::RepoDetails, + repo_details: &git::RepoDetails, ) -> std::result::Result { - todo!("MockRepository::git_clone") + let gitdir = &repo_details.gitdir; + #[allow(clippy::unwrap_used)] + self.clone_repos + .lock() + .map_err(|_| crate::repository::Error::MockLock) + .map(|or| or.get(gitdir).cloned()) + .transpose() + .unwrap_or_else(|| Err(crate::repository::Error::InvalidGitDir(gitdir.clone()))) + .map(|mor| { + let repo_path = &repo_details.repo_path; + let hostname = repo_details.forge.hostname(); + mor.log(format!( + "git_clone hostname:{hostname} repo_path:{repo_path}" + )); + mor.into() + }) } } diff --git a/crates/git/src/repository/mod.rs b/crates/git/src/repository/mod.rs index 14e1b1a..c9411a9 100644 --- a/crates/git/src/repository/mod.rs +++ b/crates/git/src/repository/mod.rs @@ -1,14 +1,11 @@ // -#[cfg(test)] mod mock; -#[cfg(test)] -pub use mock::MockRepository; -#[cfg(test)] -pub use open::MockOpenRepository; +pub use mock::FakeRepository; +pub use open::FakeOpenRepository; -mod open; +pub mod open; mod real; -mod test; +pub mod test; #[cfg(test)] mod tests; @@ -35,22 +32,24 @@ use super::RepoDetails; #[allow(clippy::large_enum_variant)] pub enum Repository { Real, - #[cfg(test)] - Mock(MockRepository), Test(TestRepository), + Mock(FakeRepository), } pub const fn new() -> Repository { Repository::Real } -#[cfg(test)] -pub fn mock() -> MockRepository { - MockRepository::new() +pub fn mock() -> FakeRepository { + FakeRepository::new() } -pub const fn test(fs: kxio::fs::FileSystem) -> TestRepository { - TestRepository::new(false, fs, vec![], vec![]) +pub fn new_test(fs: kxio::fs::FileSystem, hostname: config::Hostname) -> Repository { + Repository::Test(test(fs, hostname)) +} + +pub fn test(fs: kxio::fs::FileSystem, hostname: config::Hostname) -> TestRepository { + TestRepository::new(false, hostname, fs, Default::default(), Default::default()) } // #[cfg(test)] @@ -66,15 +65,20 @@ pub fn open( repo_details: &RepoDetails, gitdir: config::GitDir, ) -> Result { + println!("validating repo in {gitdir:?}"); let repository = if !gitdir.exists() { + println!("dir doesn't exist - cloning..."); info!("Local copy not found - cloning..."); repository.git_clone(repo_details)? } else { + println!("dir exists - opening..."); info!("Local copy found - opening..."); repository.open(&gitdir)? }; + println!("open - validating"); info!("Validating..."); validate_repo(&repository, repo_details).map_err(|e| Error::Validation(e.to_string()))?; + println!("open - validated - okay"); Ok(repository) } @@ -88,10 +92,8 @@ impl std::ops::Deref for Repository { fn deref(&self) -> &Self::Target { match self { Self::Real => &real::RealRepository, - Self::Test(test_repository) => test_repository, - - #[cfg(test)] - Self::Mock(mock_repository) => mock_repository, + Self::Test(test) => test, + Self::Mock(mock) => mock, } } } @@ -134,6 +136,9 @@ pub enum Error { #[error("git clone: {0}")] Clone(String), + #[error("init: {0}")] + Init(String), + #[error("open: {0}")] Open(String), diff --git a/crates/git/src/repository/open/mod.rs b/crates/git/src/repository/open/mod.rs index 0082fe0..90560e7 100644 --- a/crates/git/src/repository/open/mod.rs +++ b/crates/git/src/repository/open/mod.rs @@ -7,7 +7,6 @@ pub mod oreal; pub mod otest; -#[cfg(test)] pub mod omock; use std::{ @@ -18,12 +17,12 @@ use std::{ use crate as git; use git::repository::Direction; use git_next_config as config; -#[cfg(test)] -pub use omock::MockOpenRepository; +pub use omock::FakeOpenRepository; pub use oreal::RealOpenRepository; pub use otest::TestOpenRepository; #[derive(Clone, Debug)] +#[allow(clippy::large_enum_variant)] pub enum OpenRepository { /// A real git repository. /// @@ -44,8 +43,7 @@ pub enum OpenRepository { /// the behaviour of a git repository. Once the [Self::LocalOnly] /// variant is ready for use, tests should be converted to using /// that instead. - #[cfg(test)] - Mock(git::repository::MockOpenRepository), // TODO: (#38) contain a mock model of a repo + Fake(git::repository::FakeOpenRepository), // TODO: (#38) contain a mock model of a repo } pub fn real(gix_repo: gix::Repository) -> OpenRepository { @@ -57,23 +55,30 @@ pub fn real(gix_repo: gix::Repository) -> OpenRepository { #[cfg(not(tarpaulin_include))] // don't test mocks pub fn test( gitdir: &config::GitDir, + hostname: &config::Hostname, fs: kxio::fs::FileSystem, - on_fetch: Vec, - on_push: Vec, + on_fetch: Arc>>, + on_push: Arc>>, ) -> OpenRepository { - OpenRepository::Test(TestOpenRepository::new(gitdir, fs, on_fetch, on_push)) + let open_repo = TestOpenRepository::new(gitdir, hostname, fs, on_fetch, on_push); + open_repo.log("test"); + OpenRepository::Test(open_repo) } #[cfg(not(tarpaulin_include))] // don't test mocks pub fn test_bare( gitdir: &config::GitDir, + hostname: &config::Hostname, fs: kxio::fs::FileSystem, - on_fetch: Vec, - on_push: Vec, + on_fetch: Arc>>, + on_push: Arc>>, ) -> OpenRepository { - OpenRepository::Test(TestOpenRepository::new_bare(gitdir, fs, on_fetch, on_push)) + let open_repo = TestOpenRepository::new_bare(gitdir, hostname, fs, on_fetch, on_push); + open_repo.log("test_bare"); + OpenRepository::Test(open_repo) } +#[cfg_attr(test, mockall::automock)] pub trait OpenRepositoryLike { fn remote_branches(&self) -> git::push::Result>; fn find_default_remote(&self, direction: Direction) -> Option; @@ -109,9 +114,7 @@ impl std::ops::Deref for OpenRepository { match self { Self::Real(real) => real, Self::Test(test) => test, - - #[cfg(test)] - Self::Mock(mock) => mock, + Self::Fake(mock) => mock, } } } diff --git a/crates/git/src/repository/open/omock.rs b/crates/git/src/repository/open/omock.rs index be1968e..3c0073b 100644 --- a/crates/git/src/repository/open/omock.rs +++ b/crates/git/src/repository/open/omock.rs @@ -1,21 +1,33 @@ // -use crate as git; +#![cfg(not(tarpaulin_include))] + +use crate::{self as git, RepoDetails}; use git_next_config as config; use std::{ + collections::HashMap, path::Path, sync::{Arc, Mutex}, }; #[derive(Clone, Debug, Default)] -pub struct MockOpenRepository { +pub struct FakeOpenRepository { + log: Arc>>, default_push_remote: Arc>>, default_fetch_remote: Arc>>, - operations: Arc>>, + commit_logs: HashMap>, + repo_details: RepoDetails, } -impl MockOpenRepository { - pub fn new() -> Self { - Self::default() +impl FakeOpenRepository { + pub fn new(repo_details: RepoDetails) -> Self { + Self { + repo_details, + ..Default::default() + } + } + pub fn with_default_remote(mut self, direction: git::repository::Direction) -> Self { + self.given_has_default_remote(direction, Some(self.repo_details.git_remote())); + self } pub fn given_has_default_remote( &mut self, @@ -43,20 +55,39 @@ impl MockOpenRepository { }; } - pub fn operations(&self) -> Vec { - self.operations + pub fn with_commit_log( + mut self, + branch_name: config::BranchName, + commits: Vec, + ) -> Self { + self.commit_logs.insert(branch_name, commits); + self + } + + pub fn log(&self, message: impl Into) { + let message: String = message.into(); + let _ = self.log.lock().map(|mut log| log.push(message)); + } + + pub fn take_log(&mut self) -> Vec { + println!("take_log: {:#?}", self.log); + self.log .lock() - .map(|operations| operations.clone()) + .map(|mut self_log| { + let out_log: Vec = self_log.clone(); + self_log.clear(); + out_log + }) .unwrap_or_default() } } -impl From for git::OpenRepository { - fn from(value: MockOpenRepository) -> Self { - Self::Mock(value) +impl From for git::OpenRepository { + fn from(value: FakeOpenRepository) -> Self { + Self::Fake(value) } } #[allow(clippy::unwrap_used)] -impl git::repository::OpenRepositoryLike for MockOpenRepository { +impl git::repository::OpenRepositoryLike for FakeOpenRepository { fn remote_branches(&self) -> git::push::Result> { todo!("MockOpenRepository::remote_branched") } @@ -76,10 +107,7 @@ impl git::repository::OpenRepositoryLike for MockOpenRepository { } fn fetch(&self) -> core::result::Result<(), crate::fetch::Error> { - self.operations - .lock() - .map_err(|_| crate::fetch::Error::Lock) - .map(|mut operations| operations.push("fetch".to_string()))?; + self.log("fetch"); Ok(()) } @@ -92,23 +120,22 @@ impl git::repository::OpenRepositoryLike for MockOpenRepository { ) -> core::result::Result<(), crate::push::Error> { let forge_alias = repo_details.forge.forge_alias(); let repo_alias = &repo_details.repo_alias; - self.operations - .lock() - .map_err(|_| crate::fetch::Error::Lock) - .map(|mut operations| { - operations.push(format!( - "push fa:{forge_alias} ra:{repo_alias} bn:{branch_name} tc:{to_commit} f:{force}" - )) - })?; + self.log(format!( + "push fa:{forge_alias} ra:{repo_alias} bn:{branch_name} tc:{to_commit} f:{force}" + )); Ok(()) } fn commit_log( &self, - _branch_name: &git_next_config::BranchName, - _find_commits: &[git::Commit], + branch_name: &git_next_config::BranchName, + find_commits: &[git::Commit], ) -> core::result::Result, git::commit::log::Error> { - todo!("MockOpenRepository::commit_log") + self.log(format!("commit_log {branch_name} {find_commits:?}")); + self.commit_logs + .get(branch_name) + .cloned() + .ok_or(git::commit::log::Error::Lock) } fn read_file( diff --git a/crates/git/src/repository/open/otest.rs b/crates/git/src/repository/open/otest.rs index 7720011..5dd5549 100644 --- a/crates/git/src/repository/open/otest.rs +++ b/crates/git/src/repository/open/otest.rs @@ -1,4 +1,6 @@ // +#![cfg(not(tarpaulin_include))] + use crate as git; use derive_more::{Constructor, Deref}; use git_next_config as config; @@ -61,9 +63,10 @@ impl OnPush { #[derive(Clone, Debug)] pub struct TestOpenRepository { - on_fetch: Vec, + log: Arc>>, + on_fetch: Arc>>, fetch_counter: Arc>, - on_push: Vec, + on_push: Arc>>, push_counter: Arc>, real: git::repository::RealOpenRepository, } @@ -77,19 +80,25 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository { } fn fetch(&self) -> Result<(), git::fetch::Error> { + self.log("fetch"); let i: usize = *self .fetch_counter .read() .map_err(|_| git::fetch::Error::Lock)? .deref(); - eprintln!("Fetch: {i}"); + println!("Fetch: {i}"); self.fetch_counter .write() .map_err(|_| git::fetch::Error::Lock) .map(|mut c| *c += 1)?; - self.on_fetch.get(i).map(|f| f.invoke()).unwrap_or_else(|| { - unimplemented!("Unexpected fetch"); - }) + self.on_fetch + .lock() + .map_err(|_| git::fetch::Error::Lock) + .map(|a| { + a.get(i).map(|f| f.invoke()).unwrap_or_else(|| { + unimplemented!("Unexpected fetch"); + }) + })? } fn push( @@ -99,22 +108,29 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository { to_commit: &git::GitRef, force: &git::push::Force, ) -> git::push::Result<()> { + self.log(format!( + "push branch:{branch_name} to:{to_commit} force:{force}" + )); let i: usize = *self .push_counter .read() .map_err(|_| git::fetch::Error::Lock)? .deref(); - eprintln!("Push: {i}"); + println!("Push: {i}"); self.push_counter .write() .map_err(|_| git::fetch::Error::Lock) .map(|mut c| *c += 1)?; self.on_push - .get(i) - .map(|f| f.invoke(repo_details, branch_name, to_commit, force)) - .unwrap_or_else(|| { - unimplemented!("Unexpected push"); - }) + .lock() + .map_err(|_| git::fetch::Error::Lock) + .map(|a| { + a.get(i) + .map(|f| f.invoke(repo_details, branch_name, to_commit, force)) + .unwrap_or_else(|| { + unimplemented!("Unexpected push"); + }) + })? } fn commit_log( @@ -130,21 +146,31 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository { branch_name: &config::BranchName, file_name: &Path, ) -> git::file::Result { + self.log(format!("read_file branch:{branch_name} file:{file_name:?}")); self.real.read_file(branch_name, file_name) } } impl TestOpenRepository { pub fn new( gitdir: &config::GitDir, + hostname: &config::Hostname, fs: kxio::fs::FileSystem, - on_fetch: Vec, - on_push: Vec, + on_fetch: Arc>>, + on_push: Arc>>, ) -> Self { let pathbuf = fs.base().join(gitdir.to_path_buf()); + + // use std::os::unix::process::CommandExt as _; + // #[allow(clippy::expect_used)] + // std::process::Command::new("ls") + // .args(["-la".into(), pathbuf.clone()]) + // .exec(); + #[allow(clippy::expect_used)] - let gix = gix::init(pathbuf).expect("git init"); - Self::write_origin(gitdir, &fs); + let gix = gix::discover(pathbuf).expect("failed to open git repo"); + Self::write_origin(gitdir, hostname, &fs); Self { + log: Arc::new(Mutex::new(vec![format!("new gitdir:{gitdir:?}")])), on_fetch, fetch_counter: Arc::new(RwLock::new(0)), on_push, @@ -154,15 +180,17 @@ impl TestOpenRepository { } pub fn new_bare( gitdir: &config::GitDir, + hostname: &config::Hostname, fs: kxio::fs::FileSystem, - on_fetch: Vec, - on_push: Vec, + on_fetch: Arc>>, + on_push: Arc>>, ) -> Self { let pathbuf = fs.base().join(gitdir.to_path_buf()); #[allow(clippy::expect_used)] let gix = gix::init_bare(pathbuf).expect("git init bare"); - Self::write_origin(gitdir, &fs); + Self::write_origin(gitdir, hostname, &fs); Self { + log: Arc::new(Mutex::new(vec![format!("new bare gitdir:{gitdir:?}")])), on_fetch, fetch_counter: Arc::new(RwLock::new(0)), on_push, @@ -171,7 +199,11 @@ impl TestOpenRepository { } } - fn write_origin(gitdir: &config::GitDir, fs: &kxio::fs::FileSystem) { + fn write_origin( + gitdir: &config::GitDir, + hostname: &config::Hostname, + fs: &kxio::fs::FileSystem, + ) { let config_file = fs.base().join(gitdir.to_path_buf()).join(".git/config"); #[allow(clippy::expect_used)] let contents = fs @@ -180,7 +212,7 @@ impl TestOpenRepository { let updated_contents = format!( r#"{contents} [remote "origin"] - url = git@foo.example,net + url = git@{hostname} fetch = +refs/heads/*:refs/remotes/origin/* "# ); @@ -188,4 +220,21 @@ impl TestOpenRepository { fs.file_write(&config_file, &updated_contents) .expect("write updated .git/config") } + + pub fn log(&self, message: impl Into) { + let message: String = message.into(); + let _ = self.log.lock().map(|mut log| log.push(message)); + } + + pub fn take_log(&mut self) -> Vec { + println!("take_log: {:#?}", self.log); + self.log + .lock() + .map(|mut self_log| { + let out_log: Vec = self_log.clone(); + self_log.clear(); + out_log + }) + .unwrap_or_default() + } } diff --git a/crates/git/src/repository/open/tests.rs b/crates/git/src/repository/open/tests.rs index 1afedd6..e3080d2 100644 --- a/crates/git/src/repository/open/tests.rs +++ b/crates/git/src/repository/open/tests.rs @@ -321,7 +321,7 @@ mod remote_branches { fn should_return_remote_branches() -> TestResult { let_assert!(Ok(fs) = kxio::fs::temp()); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); let repo_config = &given::a_repo_config(); let branches = repo_config.branches(); @@ -347,7 +347,7 @@ mod commit_log { fn should_return_single_item_in_commit_log_when_not_searching() -> TestResult { let_assert!(Ok(fs) = kxio::fs::temp()); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); let repo_config = &given::a_repo_config(); let branches = repo_config.branches(); @@ -364,7 +364,7 @@ mod commit_log { let_assert!(Ok(fs) = kxio::fs::temp()); let branch_name = given::a_branch_name(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); for _ in [0; 60] { // create 60 commits @@ -381,7 +381,7 @@ mod commit_log { let_assert!(Ok(fs) = kxio::fs::temp(), "create temp directory"); let branch_name = given::a_branch_name(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); let_assert!( Ok(open_repository) = test_repository.open(&gitdir), "open repository" @@ -425,7 +425,7 @@ mod read_file { let contents = given::a_name(); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); then::commit_named_file_to_branch( &file_name, @@ -448,7 +448,7 @@ mod read_file { fn should_error_on_missing_file() -> TestResult { let_assert!(Ok(fs) = kxio::fs::temp()); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); let_assert!(Ok(open_repository) = test_repository.open(&gitdir)); let repo_config = &given::a_repo_config(); let branches = repo_config.branches(); @@ -457,7 +457,7 @@ mod read_file { Err(err) = open_repository.read_file(&branches.dev(), &given::a_pathbuf()), "read file" ); - eprintln!("err: {err:#?}"); + println!("err: {err:#?}"); assert!(matches!(err, git::file::Error::FileNotFound)); Ok(()) } diff --git a/crates/git/src/repository/test.rs b/crates/git/src/repository/test.rs index 7331f84..44d0072 100644 --- a/crates/git/src/repository/test.rs +++ b/crates/git/src/repository/test.rs @@ -1,24 +1,33 @@ // -use derive_more::Constructor; +#![cfg(not(tarpaulin_include))] -use crate as git; +use derive_more::Constructor; +use std::sync::{Arc, Mutex}; + +use crate::{self as git, OpenRepository}; use git::repository::RepositoryLike; -use git_next_config as config; +use git_next_config::{self as config}; #[derive(Clone, Debug, Constructor)] pub struct TestRepository { is_bare: bool, + hostname: config::Hostname, fs: kxio::fs::FileSystem, - on_fetch: Vec, - on_push: Vec, + on_fetch: Arc>>, + on_push: Arc>>, } impl TestRepository { + pub fn init(&self, gitdir: &config::GitDir) -> Result { + gix::init(gitdir.to_path_buf()) + .map_err(|e| git::repository::Error::Init(e.to_string())) + .map(git::repository::open::real) + } pub fn on_fetch(&mut self, on_fetch: git::repository::OnFetch) { - self.on_fetch.push(on_fetch); + let _ = self.on_fetch.lock().map(|mut a| a.push(on_fetch)); } pub fn on_push(&mut self, on_push: git::repository::OnPush) { - self.on_push.push(on_push); + let _ = self.on_push.lock().map(|mut a| a.push(on_push)); } pub const fn fs(&self) -> &kxio::fs::FileSystem { @@ -30,6 +39,7 @@ impl RepositoryLike for TestRepository { if self.is_bare { Ok(git::repository::open::test_bare( gitdir, + &self.hostname, self.fs.clone(), self.on_fetch.clone(), self.on_push.clone(), @@ -37,6 +47,7 @@ impl RepositoryLike for TestRepository { } else { Ok(git::repository::open::test( gitdir, + &self.hostname, self.fs.clone(), self.on_fetch.clone(), self.on_push.clone(), @@ -44,10 +55,8 @@ impl RepositoryLike for TestRepository { } } - fn git_clone( - &self, - _repo_details: &crate::RepoDetails, - ) -> super::Result { - todo!() + fn git_clone(&self, repo_details: &crate::RepoDetails) -> super::Result { + let gitdir = &repo_details.gitdir; + self.open(gitdir) } } diff --git a/crates/git/src/repository/tests.rs b/crates/git/src/repository/tests.rs index 337df18..cff59ee 100644 --- a/crates/git/src/repository/tests.rs +++ b/crates/git/src/repository/tests.rs @@ -1,141 +1,83 @@ use crate as git; mod validate { - use crate::{validation::repo::validate_repo, GitRemote, RepoDetails}; + use crate::{tests::given, validation::repo::validate_repo}; use super::*; use assert2::let_assert; - use git_next_config::{ForgeDetails, GitDir, Hostname, RepoPath}; #[test] fn should_ok_a_valid_repo() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let gitdir = &repo_details.gitdir; let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Push, Some(remote.clone())); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } + mock_repository + .given_can_be_opened(repo_details.clone()) + .with_default_remote(git::repository::Direction::Push) + .with_default_remote(git::repository::Direction::Fetch); let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); + let_assert!(Ok(open_repository) = repository.open(gitdir)); let_assert!(Ok(_) = validate_repo(&open_repository, &repo_details)); } #[test] fn should_fail_where_no_default_push_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let gitdir = &repo_details.gitdir; let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo.given_has_default_remote(git::repository::Direction::Push, None); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } + mock_repository + .given_can_be_opened(repo_details.clone()) + .with_default_remote(git::repository::Direction::Fetch); let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); + let_assert!(Ok(open_repository) = repository.open(gitdir)); let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); } + #[test] fn should_fail_where_no_default_fetch_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let gitdir = &repo_details.gitdir; let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo.given_has_default_remote(git::repository::Direction::Push, None); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } + mock_repository + .given_can_be_opened(repo_details.clone()) + .with_default_remote(git::repository::Direction::Push); let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); + let_assert!(Ok(open_repository) = repository.open(gitdir)); let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); } + #[test] fn should_fail_where_invalid_default_push_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - let other_remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/other".to_string()), - ); - + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let gitdir = &repo_details.gitdir; + let other_remote = given::repo_details(&fs).git_remote(); let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Push, Some(other_remote)); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(remote)); - } + mock_repository + .given_can_be_opened(repo_details.clone()) + .with_default_remote(git::repository::Direction::Fetch) + .given_has_default_remote(git::repository::Direction::Push, Some(other_remote)); let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); + let_assert!(Ok(open_repository) = repository.open(gitdir)); let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); } + #[test] fn should_fail_where_invalid_default_fetch_remote() { - let repo_details = RepoDetails::default() - .with_forge( - ForgeDetails::default().with_hostname(Hostname::new("localhost".to_string())), - ) - .with_repo_path(RepoPath::new("kemitix/test".to_string())); - let gitdir = GitDir::from("foo"); - let remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/test".to_string()), - ); - let other_remote = GitRemote::new( - Hostname::new("localhost"), - RepoPath::new("kemitix/other".to_string()), - ); - + let fs = given::a_filesystem(); + let repo_details = given::repo_details(&fs); + let gitdir = &repo_details.gitdir; + let other_remote = given::repo_details(&fs).git_remote(); let mut mock_repository = git::repository::mock(); - { - let mut mock_open_repo = mock_repository.given_can_be_opened(&gitdir); - mock_open_repo.given_has_default_remote(git::repository::Direction::Push, Some(remote)); - mock_open_repo - .given_has_default_remote(git::repository::Direction::Fetch, Some(other_remote)); - } + mock_repository + .given_can_be_opened(repo_details.clone()) + .with_default_remote(git::repository::Direction::Push) + .given_has_default_remote(git::repository::Direction::Fetch, Some(other_remote)); let (repository, _mock_repository) = mock_repository.seal(); - let_assert!(Ok(open_repository) = repository.open(&gitdir)); + let_assert!(Ok(open_repository) = repository.open(gitdir)); let_assert!(Err(_) = validate_repo(&open_repository, &repo_details)); } } diff --git a/crates/git/src/tests.rs b/crates/git/src/tests.rs index 40dbb13..58d9fd6 100644 --- a/crates/git/src/tests.rs +++ b/crates/git/src/tests.rs @@ -108,9 +108,9 @@ mod push { #[test] fn should_perform_a_fetch_then_push() { let fs = given::a_filesystem(); - let (mock_open_repository, gitdir, mock_repository) = given::an_open_repository(&fs); + let (mock_open_repository, repo_details, mock_repository) = + given::an_open_repository(&fs); let open_repository: OpenRepository = mock_open_repository.into(); - let repo_details = given::repo_details(&fs); let branch_name = &repo_details.branch; let commit = given::a_commit(); let gitref = GitRef::from(commit); @@ -123,14 +123,14 @@ mod push { &git::push::Force::No ) ); - let_assert!(Some(mock_open_repository) = mock_repository.get(&gitdir)); - let operations = mock_open_repository.operations(); + let_assert!(Some(mut mock_open_repository) = mock_repository.get(&repo_details.gitdir)); + let log = mock_open_repository.take_log(); let forge_alias = repo_details.forge.forge_alias(); let repo_alias = &repo_details.repo_alias; let to_commit = gitref; let force = "fast-forward"; assert_eq!( - operations, + log, vec![format!("fetch"), format!("push fa:{forge_alias} ra:{repo_alias} bn:{branch_name} tc:{to_commit} f:{force}")] ); } @@ -215,9 +215,10 @@ pub mod given { // use crate::{ self as git, - repository::{MockOpenRepository, MockRepository}, + repository::{FakeOpenRepository, FakeRepository}, tests::given, }; + use config::{ BranchName, ForgeAlias, ForgeConfig, ForgeType, GitDir, RepoAlias, RepoBranches, RepoConfig, ServerRepoConfig, @@ -348,11 +349,15 @@ pub mod given { pub fn an_open_repository( fs: &kxio::fs::FileSystem, - ) -> (MockOpenRepository, GitDir, MockRepository) { + ) -> (FakeOpenRepository, RepoDetails, FakeRepository) { let mut mock = git::repository::mock(); - let gitdir = a_git_dir(fs); - let or = mock.given_can_be_opened(&gitdir); - (or, gitdir, mock) + let repo_details = given::repo_details(fs); + let or = mock.given_can_be_opened(repo_details.clone()); + (or, repo_details, mock) + } + + pub fn a_hostname() -> git_next_config::Hostname { + config::Hostname::new(given::a_name()) } } pub mod then { @@ -455,22 +460,22 @@ pub mod then { } fn exec(label: String, output: Result) -> TestResult { - eprintln!("== {label}"); + println!("== {label}"); match output { Ok(output) => { - eprintln!( + println!( "\nstdout:\n{}", String::from_utf8_lossy(output.stdout.as_slice()) ); - eprintln!( + println!( "\nstderr:\n{}", String::from_utf8_lossy(output.stderr.as_slice()) ); - eprintln!("============================="); + println!("============================="); Ok(()) } Err(err) => { - eprintln!("ERROR: {err:#?}"); + println!("ERROR: {err:#?}"); Ok(Err(err)?) } } diff --git a/crates/git/src/validation/tests.rs b/crates/git/src/validation/tests.rs index 549e82d..ed91afe 100644 --- a/crates/git/src/validation/tests.rs +++ b/crates/git/src/validation/tests.rs @@ -15,7 +15,7 @@ mod repos { fn where_repo_has_no_push_remote() { let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let test_repository = git::repository::test(fs.clone()); + let test_repository = git::repository::test(fs.clone(), given::a_hostname()); // default has no push or fetch remotes let repo_details = given::repo_details(&fs); let_assert!(Ok(repository) = test_repository.open(&gitdir), "open repo"); @@ -44,7 +44,7 @@ mod positions { fn where_fetch_fails_should_error() { let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); test_repository.on_fetch(git::repository::OnFetch::new( given::repo_branches(), gitdir.clone(), @@ -70,7 +70,7 @@ mod positions { fn where_main_branch_is_missing_or_commit_log_is_empty_should_error() { let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); test_repository.on_fetch(git::repository::OnFetch::new( given::repo_branches(), gitdir.clone(), @@ -104,7 +104,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -144,7 +144,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -186,7 +186,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -234,7 +234,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -304,7 +304,7 @@ mod positions { ); //then - eprintln!("Got: {err:?}"); + println!("Got: {err:?}"); // NOTE: assertions for correct push are in on_push above assert!(matches!( err, @@ -318,7 +318,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -371,7 +371,7 @@ mod positions { ); //then - eprintln!("Got: {err:?}"); + println!("Got: {err:?}"); let_assert!( Ok(sha_next) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().next()), @@ -389,7 +389,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -457,7 +457,7 @@ mod positions { ); //then - eprintln!("Got: {err:?}"); + println!("Got: {err:?}"); // NOTE: assertions for correct push are in on_push above assert!(matches!( err, @@ -471,7 +471,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -522,7 +522,7 @@ mod positions { ); //then - eprintln!("Got: {err:?}"); + println!("Got: {err:?}"); let_assert!( Ok(sha_next) = then::get_sha_for_branch(&fs, &gitdir, &repo_config.branches().next()), @@ -540,7 +540,7 @@ mod positions { //given let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs"); let gitdir: config::GitDir = fs.base().to_path_buf().into(); - let mut test_repository = git::repository::test(fs.clone()); + let mut test_repository = git::repository::test(fs.clone(), given::a_hostname()); let repo_config = given::a_repo_config(); test_repository.on_fetch(git::repository::OnFetch::new( repo_config.branches().clone(), @@ -571,7 +571,7 @@ mod positions { ); //then - eprintln!("positions: {positions:#?}"); + println!("positions: {positions:#?}"); let_assert!( Ok(main_sha) =