diff --git a/herostratus-tests/src/fixtures/repository.rs b/herostratus-tests/src/fixtures/repository.rs index 98531a6..6fd9cbb 100644 --- a/herostratus-tests/src/fixtures/repository.rs +++ b/herostratus-tests/src/fixtures/repository.rs @@ -55,7 +55,7 @@ pub fn with_empty_commits(messages: &[&str]) -> eyre::Result { #[cfg(test)] mod tests { use git2::{Index, Odb, Repository}; - use herostratus::git::{rev_parse, rev_walk}; + use herostratus::git; use super::*; @@ -79,8 +79,8 @@ mod tests { fn test_new_repository() { let temp_repo = simplest().unwrap(); - let rev = rev_parse("HEAD", &temp_repo.repo).unwrap(); - let commits: Vec<_> = rev_walk(rev, &temp_repo.repo) + let rev = git::rev::parse("HEAD", &temp_repo.repo).unwrap(); + let commits: Vec<_> = git::rev::walk(rev, &temp_repo.repo) .unwrap() .map(|oid| temp_repo.repo.find_commit(oid.unwrap()).unwrap()) .collect(); diff --git a/herostratus/src/achievement/mod.rs b/herostratus/src/achievement/mod.rs index 95fe698..7d9d0a1 100644 --- a/herostratus/src/achievement/mod.rs +++ b/herostratus/src/achievement/mod.rs @@ -2,8 +2,6 @@ #[allow(clippy::module_inception)] mod achievement; mod process_rules; -#[cfg(test)] -mod test_process_rules; pub use achievement::{Achievement, LoggedRule, Rule, RuleFactory}; pub use process_rules::{grant, grant_with_rules}; diff --git a/herostratus/src/achievement/process_rules.rs b/herostratus/src/achievement/process_rules.rs index 385773f..8a9d852 100644 --- a/herostratus/src/achievement/process_rules.rs +++ b/herostratus/src/achievement/process_rules.rs @@ -157,10 +157,10 @@ pub fn grant_with_rules<'repo>( repo: &'repo git2::Repository, rules: Vec>, ) -> eyre::Result + 'repo> { - let rev = crate::git::rev_parse(reference, repo) + let rev = crate::git::rev::parse(reference, repo) .wrap_err(format!("Failed to rev-parse: {reference:?}"))?; let oids = - crate::git::rev_walk(rev, repo).wrap_err(format!("Failed to rev-walk rev: {rev:?}"))?; + crate::git::rev::walk(rev, repo).wrap_err(format!("Failed to rev-walk rev: {rev:?}"))?; // TODO: There should be better error handling than this let oids = oids.filter_map(|o| match o { @@ -172,3 +172,52 @@ pub fn grant_with_rules<'repo>( }); Ok(process_rules(oids, repo, rules)) } + +#[cfg(test)] +mod tests { + use herostratus_tests::fixtures; + + use super::*; + use crate::rules::test_rules::{AlwaysFail, ParticipationTrophy, ParticipationTrophy2}; + + #[test] + fn test_no_rules() { + let temp_repo = fixtures::repository::simplest().unwrap(); + let rules = Vec::new(); + let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); + let achievements: Vec<_> = achievements.collect(); + assert!(achievements.is_empty()); + } + + #[test] + fn test_iterator_no_matches() { + let temp_repo = fixtures::repository::simplest().unwrap(); + let rules = vec![Box::new(AlwaysFail) as Box]; + let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); + let achievements: Vec<_> = achievements.collect(); + assert!(achievements.is_empty()); + } + + #[test] + fn test_iterator_all_matches() { + let temp_repo = fixtures::repository::simplest().unwrap(); + + let rules = vec![ + Box::new(AlwaysFail) as Box, + Box::new(ParticipationTrophy) as Box, + ]; + let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); + let achievements: Vec<_> = achievements.collect(); + assert_eq!(achievements.len(), 1); + } + + #[test] + fn test_awards_on_finalize() { + let temp_repo = fixtures::repository::simplest().unwrap(); + + let rules = vec![Box::new(ParticipationTrophy2) as Box]; + let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); + let achievements: Vec<_> = achievements.collect(); + assert_eq!(achievements.len(), 1); + } +} diff --git a/herostratus/src/achievement/test_process_rules.rs b/herostratus/src/achievement/test_process_rules.rs deleted file mode 100644 index b3a5e55..0000000 --- a/herostratus/src/achievement/test_process_rules.rs +++ /dev/null @@ -1,45 +0,0 @@ -use herostratus_tests::fixtures; - -use crate::achievement::{grant_with_rules, Rule}; -use crate::rules::test_rules::{AlwaysFail, ParticipationTrophy, ParticipationTrophy2}; - -#[test] -fn test_no_rules() { - let temp_repo = fixtures::repository::simplest().unwrap(); - let rules = Vec::new(); - let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); - let achievements: Vec<_> = achievements.collect(); - assert!(achievements.is_empty()); -} - -#[test] -fn test_iterator_no_matches() { - let temp_repo = fixtures::repository::simplest().unwrap(); - let rules = vec![Box::new(AlwaysFail) as Box]; - let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); - let achievements: Vec<_> = achievements.collect(); - assert!(achievements.is_empty()); -} - -#[test] -fn test_iterator_all_matches() { - let temp_repo = fixtures::repository::simplest().unwrap(); - - let rules = vec![ - Box::new(AlwaysFail) as Box, - Box::new(ParticipationTrophy) as Box, - ]; - let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); - let achievements: Vec<_> = achievements.collect(); - assert_eq!(achievements.len(), 1); -} - -#[test] -fn test_awards_on_finalize() { - let temp_repo = fixtures::repository::simplest().unwrap(); - - let rules = vec![Box::new(ParticipationTrophy2) as Box]; - let achievements = grant_with_rules("HEAD", &temp_repo.repo, rules).unwrap(); - let achievements: Vec<_> = achievements.collect(); - assert_eq!(achievements.len(), 1); -} diff --git a/herostratus/src/config/config.rs b/herostratus/src/config/config.rs index 0691be6..a80246b 100644 --- a/herostratus/src/config/config.rs +++ b/herostratus/src/config/config.rs @@ -105,3 +105,113 @@ pub fn write_config(data_dir: &Path, config: &Config) -> eyre::Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::path::PathBuf; + + use herostratus_tests::fixtures::config::empty; + + use super::*; + + #[test] + fn default_config_toml_contents() { + let default = Config::default(); + let contents = serialize_config(&default).unwrap(); + let expected = "[repositories]\n"; + assert_eq!(contents, expected); + } + + #[test] + fn read_write_config() { + let mut repositories = HashMap::new(); + let config = RepositoryConfig { + path: PathBuf::from("git/Notgnoshi/herostratus"), + branch: None, + url: String::from("git@github.com:Notgnoshi/herostratus.git"), + ..Default::default() + }; + repositories.insert(String::from("herostratus"), config); + let config = Config { + repositories, + ..Default::default() + }; + + let fixture = empty().unwrap(); + write_config(&fixture.data_dir, &config).unwrap(); + + let contents = std::fs::read_to_string(config_path(&fixture.data_dir)).unwrap(); + let expected = "[repositories.herostratus]\n\ + path = \"git/Notgnoshi/herostratus\"\n\ + url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ + "; + assert_eq!(contents, expected); + + let read_config = read_config(&fixture.data_dir).unwrap(); + assert_eq!(read_config, config); + } + + #[test] + fn generates_default_config_if_missing() { + let fixture = empty().unwrap(); + let config_file = config_path(&fixture.data_dir); + assert!(!config_file.exists()); + + let config = read_config(&fixture.data_dir).unwrap(); + let default_config = Config::default(); + assert_eq!(config, default_config); + } + + #[test] + fn config_exclude_rules() { + let config_toml = "[repositories.herostratus]\n\ + path = \"git/Notgnoshi/herostratus\"\n\ + url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ + [rules]\n\ + exclude = [\"H4-non-unicode\"]\n\ + "; + + let config = deserialize_config(config_toml).unwrap(); + assert_eq!(config.rules.unwrap().exclude.unwrap(), ["H4-non-unicode"]); + } + + #[test] + fn rule_specific_config() { + let config_toml = "[repositories.herostratus]\n\ + path = \"git/Notgnoshi/herostratus\"\n\ + url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ + [rules]\n\ + h2_shortest_subject_line.length_threshold = 80\n\ + "; + + let config = deserialize_config(config_toml).unwrap(); + assert_eq!( + config + .rules + .unwrap() + .h2_shortest_subject_line + .unwrap() + .length_threshold, + 80 + ); + + let config_toml = "[repositories.herostratus]\n\ + path = \"git/Notgnoshi/herostratus\"\n\ + url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ + [rules.h2_shortest_subject_line]\n\ + length_threshold = 80\n\ + "; + + let config = deserialize_config(config_toml).unwrap(); + assert_eq!( + config + .rules + .unwrap() + .h2_shortest_subject_line + .unwrap() + .length_threshold, + 80 + ); + } +} diff --git a/herostratus/src/config/mod.rs b/herostratus/src/config/mod.rs index 66c8629..ed42bba 100644 --- a/herostratus/src/config/mod.rs +++ b/herostratus/src/config/mod.rs @@ -1,7 +1,5 @@ #[allow(clippy::module_inception)] mod config; -#[cfg(test)] -mod test_config; pub use config::{ config_path, deserialize_config, read_config, serialize_config, write_config, Config, diff --git a/herostratus/src/config/test_config.rs b/herostratus/src/config/test_config.rs deleted file mode 100644 index 5fa4e39..0000000 --- a/herostratus/src/config/test_config.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::collections::HashMap; -use std::path::PathBuf; - -use herostratus_tests::fixtures::config::empty; - -use crate::config::{ - config_path, deserialize_config, read_config, serialize_config, write_config, Config, - RepositoryConfig, -}; - -#[test] -fn default_config_toml_contents() { - let default = Config::default(); - let contents = serialize_config(&default).unwrap(); - let expected = "[repositories]\n"; - assert_eq!(contents, expected); -} - -#[test] -fn read_write_config() { - let mut repositories = HashMap::new(); - let config = RepositoryConfig { - path: PathBuf::from("git/Notgnoshi/herostratus"), - branch: None, - url: String::from("git@github.com:Notgnoshi/herostratus.git"), - ..Default::default() - }; - repositories.insert(String::from("herostratus"), config); - let config = Config { - repositories, - ..Default::default() - }; - - let fixture = empty().unwrap(); - write_config(&fixture.data_dir, &config).unwrap(); - - let contents = std::fs::read_to_string(config_path(&fixture.data_dir)).unwrap(); - let expected = "[repositories.herostratus]\n\ - path = \"git/Notgnoshi/herostratus\"\n\ - url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ - "; - assert_eq!(contents, expected); - - let read_config = read_config(&fixture.data_dir).unwrap(); - assert_eq!(read_config, config); -} - -#[test] -fn generates_default_config_if_missing() { - let fixture = empty().unwrap(); - let config_file = config_path(&fixture.data_dir); - assert!(!config_file.exists()); - - let config = read_config(&fixture.data_dir).unwrap(); - let default_config = Config::default(); - assert_eq!(config, default_config); -} - -#[test] -fn config_exclude_rules() { - let config_toml = "[repositories.herostratus]\n\ - path = \"git/Notgnoshi/herostratus\"\n\ - url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ - [rules]\n\ - exclude = [\"H4-non-unicode\"]\n\ - "; - - let config = deserialize_config(config_toml).unwrap(); - assert_eq!(config.rules.unwrap().exclude.unwrap(), ["H4-non-unicode"]); -} - -#[test] -fn rule_specific_config() { - let config_toml = "[repositories.herostratus]\n\ - path = \"git/Notgnoshi/herostratus\"\n\ - url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ - [rules]\n\ - h2_shortest_subject_line.length_threshold = 80\n\ - "; - - let config = deserialize_config(config_toml).unwrap(); - assert_eq!( - config - .rules - .unwrap() - .h2_shortest_subject_line - .unwrap() - .length_threshold, - 80 - ); - - let config_toml = "[repositories.herostratus]\n\ - path = \"git/Notgnoshi/herostratus\"\n\ - url = \"git@github.com:Notgnoshi/herostratus.git\"\n\ - [rules.h2_shortest_subject_line]\n\ - length_threshold = 80\n\ - "; - - let config = deserialize_config(config_toml).unwrap(); - assert_eq!( - config - .rules - .unwrap() - .h2_shortest_subject_line - .unwrap() - .length_threshold, - 80 - ); -} diff --git a/herostratus/src/git/clone.rs b/herostratus/src/git/clone.rs index 42219c4..3728dc2 100644 --- a/herostratus/src/git/clone.rs +++ b/herostratus/src/git/clone.rs @@ -148,7 +148,7 @@ pub fn fetch_remote( if before.is_some() && before.as_ref().unwrap().id() == after.id() { tracing::debug!("... done. No new commits"); } else { - let commits = crate::git::rev_walk(after.id(), repo)?; + let commits = crate::git::rev::walk(after.id(), repo)?; let mut new_commits: usize = 0; for commit_id in commits { if let Some(before) = &before { @@ -224,3 +224,29 @@ pub fn clone_repository( ); Ok(repo) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_path_from_url() { + let url_paths = [ + ( + "git@github.com:Notgnoshi/herostratus.git", + "Notgnoshi/herostratus.git", + ), + ("domain:path", "path"), + ("ssh://git@example.com:2222/path.git", "path.git"), + ("ssh://git@example.com/path.git", "path.git"), + ("https://example.com/path", "path"), + ("file:///tmp/foo", "tmp/foo"), + ]; + + for (url, expected) in url_paths { + let expected = PathBuf::from(expected); + let actual = parse_path_from_url(url).unwrap(); + assert_eq!(expected, actual); + } + } +} diff --git a/herostratus/src/git/mod.rs b/herostratus/src/git/mod.rs index 58b1c9c..5c4192d 100644 --- a/herostratus/src/git/mod.rs +++ b/herostratus/src/git/mod.rs @@ -1,29 +1,2 @@ pub mod clone; -#[cfg(test)] -mod test_clone; - -use eyre::WrapErr; -use git2::{ObjectType, Oid, Repository, Sort}; - -pub fn rev_parse(reference: &str, repo: &Repository) -> eyre::Result { - let object = repo - .revparse_single(reference) - .wrap_err("Failed to rev-parse")?; - let oid = object.id(); - tracing::debug!( - "Resolved {reference:?} to {:?} {oid:?}", - object.kind().unwrap_or(ObjectType::Any) - ); - Ok(oid) -} - -pub fn rev_walk( - oid: Oid, - repo: &Repository, -) -> eyre::Result> + '_> { - let mut revwalk = repo.revwalk().wrap_err("Could not walk repository")?; - revwalk.set_sorting(Sort::TIME | Sort::TOPOLOGICAL)?; - revwalk.push(oid)?; - - Ok(revwalk.map(|r| r.wrap_err("Failed to yield next rev"))) -} +pub mod rev; diff --git a/herostratus/src/git/rev.rs b/herostratus/src/git/rev.rs new file mode 100644 index 0000000..6f6638b --- /dev/null +++ b/herostratus/src/git/rev.rs @@ -0,0 +1,25 @@ +use eyre::WrapErr; +use git2::{ObjectType, Oid, Repository, Sort}; + +pub fn parse(reference: &str, repo: &Repository) -> eyre::Result { + let object = repo + .revparse_single(reference) + .wrap_err("Failed to rev-parse")?; + let oid = object.id(); + tracing::debug!( + "Resolved {reference:?} to {:?} {oid:?}", + object.kind().unwrap_or(ObjectType::Any) + ); + Ok(oid) +} + +pub fn walk( + oid: Oid, + repo: &Repository, +) -> eyre::Result> + '_> { + let mut revwalk = repo.revwalk().wrap_err("Could not walk repository")?; + revwalk.set_sorting(Sort::TIME | Sort::TOPOLOGICAL)?; + revwalk.push(oid)?; + + Ok(revwalk.map(|r| r.wrap_err("Failed to yield next rev"))) +} diff --git a/herostratus/src/git/test_clone.rs b/herostratus/src/git/test_clone.rs deleted file mode 100644 index a10eaaf..0000000 --- a/herostratus/src/git/test_clone.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::path::PathBuf; - -use crate::git::clone::parse_path_from_url; - -#[test] -fn test_parse_path_from_url() { - let url_paths = [ - ( - "git@github.com:Notgnoshi/herostratus.git", - "Notgnoshi/herostratus.git", - ), - ("domain:path", "path"), - ("ssh://git@example.com:2222/path.git", "path.git"), - ("ssh://git@example.com/path.git", "path.git"), - ("https://example.com/path", "path"), - ("file:///tmp/foo", "tmp/foo"), - ]; - - for (url, expected) in url_paths { - let expected = PathBuf::from(expected); - let actual = parse_path_from_url(url).unwrap(); - assert_eq!(expected, actual); - } -}