Compare commits

...

7 Commits

Author SHA1 Message Date
Jean-Claude efc28c3bd0
Add testcases for git module
Updates the function signature of a few functions to pass the repository
or its path.
2023-10-08 21:46:25 +02:00
Jean-Claude 3325a28bd8
Use `testdir` instead of own tmpdir implementation
This crate seems well suited for our task. The code is also simpler as
we deal with a PathBuf directly.
2023-09-13 12:14:27 +02:00
Jean-Claude e0c16c2cd4
Use rstest for format_string tests to simplify testing 2023-09-12 21:17:18 +02:00
Jean-Claude 7ecbf5b5a3
Add unit tests for config 2023-09-11 22:53:19 +02:00
Jean-Claude 89235fc1b9
Add fixtures for temporary paths 2023-09-11 22:43:28 +02:00
Jean-Claude 2b14de9287
Add testing crates rstest and pretty_assertions 2023-09-10 17:29:13 +02:00
Jean-Claude a2bb209e2c
Fix library returned path to .git instead of root
It seems like git2::Repository::discover_path returns the path to `.git`
and no longer to the actual root of the repository.
2023-08-22 16:45:07 +02:00
9 changed files with 1110 additions and 125 deletions

428
Cargo.lock generated
View File

@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.0.1"
@ -81,6 +96,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -93,6 +123,37 @@ version = "3.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
[[package]]
name = "camino"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
]
[[package]]
name = "cc"
version = "1.0.79"
@ -205,6 +266,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "dlv-list"
version = "0.5.0"
@ -244,6 +311,101 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.9"
@ -255,6 +417,12 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "gimli"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
[[package]]
name = "git2"
version = "0.17.1"
@ -270,6 +438,12 @@ dependencies = [
"url",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.13.2"
@ -344,6 +518,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jobserver"
version = "0.1.26"
@ -447,17 +627,39 @@ dependencies = [
"clap",
"git2",
"kamadak-exif",
"pretty_assertions",
"rand",
"regex",
"rstest",
"rust-ini",
"testdir",
"thiserror",
]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "mutate_once"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -477,6 +679,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.30.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@ -517,12 +728,40 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project-lite"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty_assertions"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@ -547,6 +786,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.8.1"
@ -564,6 +833,41 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "relative-path"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca"
[[package]]
name = "rstest"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199"
dependencies = [
"futures",
"futures-timer",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605"
dependencies = [
"cfg-if",
"glob",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version",
"syn",
"unicode-ident",
]
[[package]]
name = "rust-ini"
version = "0.19.0"
@ -574,6 +878,21 @@ dependencies = [
"ordered-multimap",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.37.19"
@ -588,6 +907,61 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -605,6 +979,34 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sysinfo"
version = "0.26.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c18a6156d1f27a9592ee18c1a846ca8dd5c258b7179fc193ae87c74ebb666f5"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"ntapi",
"once_cell",
"winapi",
]
[[package]]
name = "testdir"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48b7965698cfb3d1ac1e6e54b4b45f5caa9e89bda223c8cf723d9cf53d7cefa7"
dependencies = [
"anyhow",
"backtrace",
"cargo_metadata",
"once_cell",
"sysinfo",
"whoami",
]
[[package]]
name = "thiserror"
version = "1.0.40"
@ -770,6 +1172,26 @@ version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
[[package]]
name = "web-sys"
version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "whoami"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
dependencies = [
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -866,3 +1288,9 @@ name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View File

@ -16,3 +16,9 @@ kamadak-exif = "0.5.5"
regex = "1.8.1"
rust-ini = "0.19.0"
thiserror = "1.0.40"
[dev-dependencies]
pretty_assertions = "1.4.0"
rand = "0.8.5"
rstest = "0.18.2"
testdir = "0.8.0"

View File

@ -8,7 +8,7 @@ use super::git;
/// Represents an archive
pub struct Archive {
path: PathBuf,
pub path: PathBuf,
pub repo: git::git2::Repository,
pub config: Config,
}
@ -21,7 +21,7 @@ impl Archive {
let path = Archive::get_root()?;
let repo = git2::Repository::init(&path)?;
let config = Archive::get_config_from_main_branch()?;
let config = Archive::get_config_from_main_branch(&path)?;
Ok(Self { path, repo, config })
}
@ -51,7 +51,7 @@ impl Archive {
/// Indicate if the archive is in a dirty state.
pub fn is_dirty(&self) -> Result<bool, ArchiveError> {
Ok(git::is_dirty(&self.repo)?)
Ok(git::is_dirty(&self.path)?)
}
/// Iteratively search archive root starting at CWD going up to /.
@ -78,9 +78,12 @@ impl Archive {
}
/// Get config by reading file from main branch.
fn get_config_from_main_branch() -> Result<Config, ArchiveError> {
fn get_config_from_main_branch(path: &PathBuf) -> Result<Config, ArchiveError> {
let main_branch = "main"; // TODO: How to figure out which main branch is, without config? :D
let out = git::show(vec![format!("{}:{}", main_branch, RELATIVE_CONFIG_FILE)])?;
let out = git::show(
path,
vec![format!("{}:{}", main_branch, RELATIVE_CONFIG_FILE)],
)?;
Ok(Config::from_string(&out)?)
}
}

View File

@ -166,7 +166,7 @@ pub fn new(
String::from("--message"),
format!("Import {} images", state.images.len()),
];
git::run(args)?;
git::run(&archive.path, args)?;
println!("Done importing {} images", state.images.len());
Ok(())
@ -193,11 +193,11 @@ fn prepare_branch_for_new_import(archive: &Archive, state: &ImportState) -> Resu
String::from("--orphan"),
String::from(&unique_branch),
];
git::run(args)?;
git::run(&archive.path, args)?;
// TODO: use git2 library
let args = vec![String::from("clean"), String::from("--force")];
git::run(args)?;
git::run(&archive.path, args)?;
// TODO: use git2 library
let args = vec![
@ -206,7 +206,7 @@ fn prepare_branch_for_new_import(archive: &Archive, state: &ImportState) -> Resu
String::from("--message"),
String::from("NEW EMPTY IMPORT"),
];
git::run(args)?;
git::run(&archive.path, args)?;
assert!(archive.get_current_branch().unwrap() == unique_branch);
Ok(())

View File

@ -9,12 +9,14 @@ use crate::error::{Config as ConfigError, ConfigFormat as ConfigFormatError};
pub static RELATIVE_CONFIG_FILE: &str = "mia.ini";
/// List all sections of the config
#[derive(Debug, PartialEq)]
pub struct Config {
pub general: General,
pub schema: Schema,
}
/// List all options of the 'General' section
#[derive(Debug, PartialEq)]
pub struct General {
pub import_branch_prefix: String,
pub import_branch_schema: String,
@ -22,6 +24,7 @@ pub struct General {
}
/// List all options of the 'Schema' section
#[derive(Debug, PartialEq)]
pub struct Schema {
pub default: String,
pub custom: HashMap<String, String>,
@ -159,7 +162,7 @@ impl Default for General {
fn default() -> Self {
Self {
import_branch_prefix: String::from("import/"),
import_branch_schema: String::from("*Sy*Sm*i*c"),
import_branch_schema: String::from("*Sy*Sm*Sd*i*c"),
main_branch: String::from("main"),
}
}
@ -178,3 +181,239 @@ impl Default for Schema {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conftest::*;
use crate::error as Error;
use pretty_assertions::assert_eq;
use rstest::{fixture, rstest};
use std::assert_matches::assert_matches;
use std::fs;
#[fixture]
pub fn default_config_string() -> String {
String::from(
"[General]
ImportBranchPrefix=import/
ImportBranchSchema=*Sy*Sm*Sd*i*c
MainBranch=main
[Schema]
Default=%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x
Event=%Y/*Sy*Sm*Sd-*Ey*Em*Ed*i/%y%m%d/%y%m%d_%H%M%S*c.*x",
)
}
#[fixture]
pub fn default_general() -> General {
General {
import_branch_prefix: String::from("import/"),
import_branch_schema: String::from("*Sy*Sm*Sd*i*c"),
main_branch: String::from("main"),
}
}
#[fixture]
pub fn default_schema() -> Schema {
let mut schema = HashMap::new();
schema.insert(
String::from("Event"),
String::from("%Y/*Sy*Sm*Sd-*Ey*Em*Ed*i/%y%m%d/%y%m%d_%H%M%S*c.*x"),
);
Schema {
default: String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x"),
custom: schema,
}
}
#[fixture]
pub fn default_config(default_general: General, default_schema: Schema) -> Config {
Config {
general: default_general,
schema: default_schema,
}
}
#[fixture]
pub fn default_config_ini(default_config_string: String) -> Ini {
Ini::load_from_str(&default_config_string).unwrap()
}
#[rstest]
fn general_default(default_general: General) {
let general = General::default();
assert_eq!(general, default_general);
}
#[rstest]
fn schema_default(default_schema: Schema) {
let schema = Schema::default();
assert_eq!(schema, default_schema);
}
#[rstest]
fn config_default(default_config: Config) {
let config = Config::default();
assert_eq!(config, default_config);
}
#[rstest]
fn config_from_file(tmp_dir: PathBuf, default_config_string: String, default_config: Config) {
// TODO: from_file also calls from_ini. Patch it?
let mut path = tmp_dir;
path.push("config");
fs::write(&path, default_config_string).expect("Failed to write to file");
let config = Config::from_file(&path).unwrap();
assert_eq!(config, default_config);
}
#[test]
fn config_from_file_invalid_path_inexisting() {
// TODO: from_file also calls from_ini. Patch it?
let mut path = PathBuf::new();
path.push("INVALID");
let config = Config::from_file(&path);
assert_matches!(config, Err(Error::Config::ReadFile { source: _ }));
}
#[rstest]
fn config_from_string(default_config_string: String, default_config: Config) {
// TODO: from_string also calls from_ini. Patch it?
let config = Config::from_string(&default_config_string).unwrap();
assert_eq!(config, default_config);
}
#[rstest]
#[case(String::from(""))]
#[case(String::from("[General]"))]
#[case(String::from(
"[General]
ImportBranchPrefix=MISSING_FIELDS
[Schema]
Default=MORE_MISSING_FIELDS"
))]
fn config_from_string_invalid(#[case] config_str: String) {
// TODO: from_string also calls from_ini. Patch it?
let config = Config::from_string(&config_str);
assert!(config.is_err());
assert!(matches!(config, Err(Error::Config::InvalidFormat(_))));
}
#[rstest]
fn config_from_ini(default_config_ini: Ini, default_config: Config) {
let config = Config::from_ini(default_config_ini).unwrap();
assert_eq!(config, default_config);
}
#[rstest]
#[case(Ini::load_from_str(&"").unwrap())]
#[case(Ini::load_from_str(&"[General]").unwrap())]
#[case(Ini::load_from_str(&"[General]
ImportBranchPrefix=MISSING_FIELDS
[Schema]
Default=MORE_MISSING_FIELDS").unwrap())]
fn config_from_ini_invalid(#[case] config_ini: Ini) {
let config = Config::from_ini(config_ini);
assert!(matches!(config, Err(Error::Config::InvalidFormat(_))));
}
#[rstest]
fn config_dump_default(tmp_dir: PathBuf, default_config_string: String) {
let mut path = tmp_dir;
path.push("dump");
assert!(Config::dump_default(&path).is_ok());
let content = fs::read_to_string(&path).expect("Failed to read from file");
let content = content.trim();
assert_eq!(content, default_config_string);
}
#[rstest]
#[case(
String::from("General"),
String::from("ImportBranchPrefix"),
String::from("import/")
)]
#[case(
String::from("General"),
String::from("ImportBranchSchema"),
String::from("*Sy*Sm*Sd*i*c")
)]
#[case(
String::from("General"),
String::from("MainBranch"),
String::from("main")
)]
#[case(
String::from("Schema"),
String::from("Default"),
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x")
)]
#[case(
String::from("Schema"),
String::from("Event"),
String::from("%Y/*Sy*Sm*Sd-*Ey*Em*Ed*i/%y%m%d/%y%m%d_%H%M%S*c.*x")
)]
fn config_read_property(
default_config_ini: Ini,
#[case] section: String,
#[case] key: String,
#[case] expected: String,
) {
let property = Config::read_property(&default_config_ini, &section, &key).unwrap();
assert_eq!(property, expected);
}
#[rstest]
fn config_read_property_invalid_section(default_config_ini: Ini) {
let section = "INVALID_SECTION";
let message: String = format!("Section '{}' is missing", section.to_owned());
let property = Config::read_property(&default_config_ini, section, &"some_key");
assert_matches!(property, Err(Error::ConfigFormat::Custom{message: m}) if m == message);
}
#[rstest]
fn config_read_property_invalid_field(default_config_ini: Ini) {
let section = "General";
let key = "INVALID_KEY";
let message: String = format!(
"Field '{}' is missing in section '{}'",
key.to_owned(),
section.to_owned()
);
let property = Config::read_property(&default_config_ini, section, key);
assert_matches!(property, Err(Error::ConfigFormat::Custom{message: m}) if m == message);
}
#[rstest]
#[case(String::from("General"), vec![
(String::from("ImportBranchPrefix"), String::from("import/")),
(String::from("ImportBranchSchema"), String::from("*Sy*Sm*Sd*i*c")),
(String::from("MainBranch"), String::from("main"))])
]
#[case(String::from("Schema"), vec![
(String::from("Default"), String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x")),
(String::from("Event"), String::from("%Y/*Sy*Sm*Sd-*Ey*Em*Ed*i/%y%m%d/%y%m%d_%H%M%S*c.*x")),
])]
fn config_read_section(
default_config_ini: Ini,
#[case] section: String,
#[case] expected: Vec<(String, String)>,
) {
let section = Config::read_section(&default_config_ini, &section).unwrap();
assert_eq!(section, expected);
}
#[rstest]
fn config_read_section_invalid_section(default_config_ini: Ini) {
let section = "INVALID_SECTION";
let message: String = format!("Section '{}' is missing", section.to_owned());
let property = Config::read_section(&default_config_ini, section);
assert_matches!(property, Err(Error::ConfigFormat::Custom{message: m}) if m == message);
}
}

27
src/conftest.rs Normal file
View File

@ -0,0 +1,27 @@
// Testing fixtures and other things
use rand::distributions::{Alphanumeric, DistString};
use rand::thread_rng;
use rstest::fixture;
use std::fs;
use std::path::PathBuf;
use testdir::testdir;
#[fixture]
/// Return path to a temporary test directory
pub fn tmp_dir() -> PathBuf {
testdir!()
}
#[fixture]
/// Return path to a temporary test file
pub fn tmp_file() -> PathBuf {
let mut path = tmp_dir();
path.push(format!(
"file_{}",
Alphanumeric.sample_string(&mut thread_rng(), 8)
));
fs::File::create(&path)
.unwrap_or_else(|_| panic!("Failed to create tmpfile {}", path.display()));
path
}

View File

@ -170,120 +170,153 @@ fn fmt_count(input: &str, count: &Option<usize>) -> String {
}
#[cfg(test)]
mod test {
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use rstest::{fixture, rstest};
#[test]
fn test_fmt_8601() {
let dt = NaiveDateTime::parse_from_str("2023-04-03 02:01:01", "%Y-%m-%d %H:%M:%S").ok();
let raw = "%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_8601(&raw, &dt);
let exp = "2023/230403*i/230403_020101*c.*x";
assert_eq!(&out, exp);
#[fixture]
/// Fixture providing DateTime of *2022-01-02 03:04:05*
fn datetime1() -> NaiveDateTime {
NaiveDateTime::parse_from_str("2022-01-02 03:04:05", "%Y-%m-%d %H:%M:%S").unwrap()
}
#[test]
fn test_fmt_custom_start_end_datetime() {
let start_dt =
NaiveDateTime::parse_from_str("2023-04-03 02:01:01", "%Y-%m-%d %H:%M:%S").ok();
let end_dt = NaiveDateTime::parse_from_str("2023-05-11 21:42:07", "%Y-%m-%d %H:%M:%S").ok();
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_start_end_datetime(&raw, &start_dt, &end_dt);
let exp = "%Y/230403_230511*i/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
#[fixture]
/// Fixture providing DateTime of *2023-11-12 13:14:15*
fn datetime2() -> NaiveDateTime {
NaiveDateTime::parse_from_str("2023-11-12 13:14:15", "%Y-%m-%d %H:%M:%S").unwrap()
}
#[test]
fn test_fmt_custom_start_end_datetime_no_start() {
let start_dt = None;
let end_dt = NaiveDateTime::parse_from_str("2023-05-11 21:42:07", "%Y-%m-%d %H:%M:%S").ok();
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_start_end_datetime(&raw, &start_dt, &end_dt);
let exp = "%Y/_230511*i/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
#[rstest]
#[case(
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x"),
String::from("2022/220102*i/220102_030405*c.*x")
)]
fn test_fmt_8601(datetime1: NaiveDateTime, #[case] raw: String, #[case] expected: String) {
let out = fmt_8601(&raw, &Some(datetime1));
assert_eq!(out, expected);
}
#[test]
fn test_fmt_custom_start_end_datetime_no_end() {
let start_dt =
NaiveDateTime::parse_from_str("2023-04-03 02:01:01", "%Y-%m-%d %H:%M:%S").ok();
let end_dt = None;
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_start_end_datetime(&raw, &start_dt, &end_dt);
let exp = "%Y/230403_*i/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
}
#[test]
fn test_fmt_custom_start_end_datetime_no_start_and_end() {
let start_dt = None;
let end_dt = None;
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_start_end_datetime(&raw, &start_dt, &end_dt);
let exp = "%Y/_*i/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
}
#[test]
fn test_fmt_custom_identifier() {
let identifier = Some(String::from("test"));
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
#[rstest]
#[case(
Some(String::from("test")),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed-test/%y%m%d_%H%M%S*c.*x")
)]
#[case(
None,
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed/%y%m%d_%H%M%S*c.*x")
)]
#[case(
Some(String::from("test")),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*i*c.*x"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed-test/%y%m%d_%H%M%S-test*c.*x")
)]
fn test_fmt_identifier(
#[case] identifier: Option<String>,
#[case] raw: String,
#[case] expected: String,
) {
let out = fmt_identifier(&raw, &identifier);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed-test/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
assert_eq!(out, expected);
}
#[test]
fn test_fmt_custom_identifier_no() {
let identifier = None;
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_identifier(&raw, &identifier);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
}
#[test]
fn test_fmt_custom_identifier_multiple() {
let identifier = Some(String::from("test"));
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/bla*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_identifier(&raw, &identifier);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed-test/bla-test/%y%m%d_%H%M%S*c.*x";
assert_eq!(&out, exp);
}
#[test]
fn test_fmt_custom_extension_small() {
let extension = Some(String::from("cr2"));
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
#[rstest]
#[case(
Some(String::from("cr2")),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.cr2")
)]
#[case(
Some(String::from("cr2")),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*X"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.CR2")
)]
#[case(
None,
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.")
)]
#[case(
None,
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*X"),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.")
)]
fn test_fmt_extension_small(
#[case] extension: Option<String>,
#[case] raw: String,
#[case] expected: String,
) {
let out = fmt_extension(&raw, &extension);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.cr2";
assert_eq!(&out, exp);
assert_eq!(out, expected);
}
#[test]
fn test_fmt_custom_extension_large() {
let extension = Some(String::from("cr2"));
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*X";
let out = fmt_extension(&raw, &extension);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.CR2";
assert_eq!(&out, exp);
#[rstest]
#[case(
Some(datetime1()),
Some(datetime2()),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/220102_231112*i/%y%m%d_%H%M%S*c.*x")
)]
#[case(
Some(datetime1()),
Some(datetime2()),
String::from("*Ey*Em*Ed*EH*EM*ES"),
String::from("231112131415")
)]
#[case(
Some(datetime1()),
Some(datetime2()),
String::from("%Y/*Sy*Sm*Sd*SH*SM*SS_*Ey*Em*Ed*EH*EM*ES*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/220102030405_231112131415*i/%y%m%d_%H%M%S*c.*x")
)]
#[case(
Some(datetime1()),
None,
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/220102_*i/%y%m%d_%H%M%S*c.*x")
)]
#[case(
None,
Some(datetime2()),
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/_231112*i/%y%m%d_%H%M%S*c.*x")
)]
#[case(
None,
None,
String::from("%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/_*i/%y%m%d_%H%M%S*c.*x")
)]
fn test_fmt_start_end_datetime(
#[case] start_dt: Option<NaiveDateTime>,
#[case] end_dt: Option<NaiveDateTime>,
#[case] raw: String,
#[case] expected: String,
) {
let out = fmt_start_end_datetime(&raw, &start_dt, &end_dt);
assert_eq!(out, expected);
}
#[test]
fn test_fmt_custom_extension_small_no() {
let extension = None;
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*x";
let out = fmt_extension(&raw, &extension);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.";
assert_eq!(&out, exp);
}
#[test]
fn test_fmt_custom_extension_large_no() {
let extension = None;
let raw = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.*X";
let out = fmt_extension(&raw, &extension);
let exp = "%Y/*Sy*Sm*Sd_*Ey*Em*Ed*i/%y%m%d_%H%M%S*c.";
assert_eq!(&out, exp);
#[rstest]
#[case(
Some(1),
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S-1.*x")
)]
#[case(
Some(10),
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S-10.*x")
)]
#[case(
None,
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S*c.*x"),
String::from("%Y/%y%m%d*i/%y%m%d_%H%M%S.*x")
)]
fn test_fmt_count(#[case] count: Option<usize>, #[case] raw: String, #[case] expected: String) {
let out = fmt_count(&raw, &count);
assert_eq!(out, expected);
}
}

View File

@ -18,13 +18,15 @@ pub fn get_root() -> Result<PathBuf, GitError> {
let ceiling_dir = Path::new("/");
// TODO: Improve error type of `discover_path` to indicate if no repo found or other error
let repo = Repository::discover_path(cwd, ceiling_dir)?;
Ok(repo)
let repo = repo.parent().expect("There should always be a parent");
Ok(PathBuf::from(repo))
}
/// Convenience wrapper to switch to a specific branch.
///
/// Branch `branch_name` must exists.
pub fn switch_branch(repo: &Repository, branch_name: &String) -> Result<(), GitError> {
pub fn switch_branch(repo: &Repository, branch_name: &str) -> Result<(), GitError> {
// No need to change branch, if we are already on the right branch
if let Ok(branch) = get_current_branch(repo) && &branch == branch_name {
println!("Your are already on the right target. No need to switch.");
@ -76,9 +78,22 @@ pub fn exists_branch(repo: &Repository, branch_name: &str) -> bool {
repo.find_branch(branch_name, BranchType::Local).is_ok()
}
/// Indicate if the repository is dirty.
///
/// Dirty is interpreted as having uncommitted changes and/or having untracked files.
/// TODO: use git2 library
pub fn is_dirty(repository: &PathBuf) -> Result<bool, GitError> {
let args = vec![String::from("status"), String::from("--porcelain")];
let out = run(repository, args)?;
Ok(!out.is_empty())
}
/// Run git command
pub fn run(args: Vec<String>) -> Result<String, GitError> {
let out = Command::new("git").args(&args).output()?;
pub fn run(repository: &PathBuf, args: Vec<String>) -> Result<String, GitError> {
let out = Command::new("git")
.args(&args)
.current_dir(repository)
.output()?;
if out.status.success() {
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
@ -94,21 +109,251 @@ pub fn run(args: Vec<String>) -> Result<String, GitError> {
}
}
/// Indicate if the repository is dirty.
///
/// Dirty is interpreted as having uncommitted changes and/or having untracked files.
/// TODO: use git2 library
pub fn is_dirty(_repo: &Repository) -> Result<bool, GitError> {
let args = vec![String::from("status"), String::from("--porcelain")];
let out = run(args)?;
Ok(!out.is_empty())
}
/// Run `git show ARGS` command.
///
/// TODO: use git2 library
pub fn show(args: Vec<String>) -> Result<String, GitError> {
pub fn show(repository: &PathBuf, args: Vec<String>) -> Result<String, GitError> {
let mut args_complete = args.to_owned();
args_complete.insert(0, String::from("show"));
run(args_complete)
run(repository, args_complete)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conftest::tmp_dir;
use pretty_assertions::assert_eq;
use rstest::{fixture, rstest};
use std::env::set_current_dir;
use std::fs;
/// Get name of current branch.
fn get_current_branch2(repo: &PathBuf) -> String {
let out = Command::new("git")
.arg("rev-parse")
.arg("--abbrev-ref")
.arg("HEAD")
.current_dir(repo)
.output()
.expect("Failed to run command");
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
/// Switch branch.
fn switch_branch2(repo: &PathBuf, branch: &str) -> () {
Command::new("git")
.arg("switch")
.arg(branch)
.current_dir(repo)
.output()
.expect("Failed to run command");
}
/// Creates three branches.
fn create_branches(repo: &PathBuf) -> () {
Command::new("git")
.arg("branch")
.arg("branch1")
.arg("main")
.current_dir(repo)
.output()
.expect("Failed to run command");
Command::new("git")
.arg("branch")
.arg("branch2")
.arg("main")
.current_dir(repo)
.output()
.expect("Failed to run command");
Command::new("git")
.arg("branch")
.arg("branch3")
.arg("main")
.current_dir(repo)
.output()
.expect("Failed to run command");
}
#[fixture]
/// Initialize new git repository at temporary path.
///
/// Changes working directory into the repo's root.
fn repository(tmp_dir: PathBuf) -> (Repository, PathBuf) {
set_current_dir(&tmp_dir).expect("Failed to change dir");
let repo = Repository::init(&tmp_dir).unwrap();
let _ = Command::new("git")
.arg("commit")
.arg("--allow-empty")
.arg("--message")
.arg("INITIAL COMMIT")
.current_dir(&tmp_dir)
.output()
.expect("Failed to run command");
(repo, tmp_dir)
}
#[rstest]
#[case(String::from(""))]
#[case(String::from("2023"))]
#[case(String::from("2023/230913"))]
#[case(String::from(".git"))]
fn test_get_root(repository: (Repository, PathBuf), #[case] relative_path: String) {
let mut repo = repository.1;
let repo_root = repo.to_owned();
repo.push(relative_path);
fs::create_dir_all(&repo).expect("Failed to create dir");
set_current_dir(&repo).expect("Failed to change dir");
let root = get_root().unwrap();
assert_eq!(root, repo_root);
}
#[rstest]
fn test_switch_branch(repository: (Repository, PathBuf)) {
create_branches(&repository.1);
assert_eq!(get_current_branch2(&repository.1), String::from("main"));
assert!(switch_branch(&repository.0, "branch1").is_ok());
assert_eq!(get_current_branch2(&repository.1), String::from("branch1"));
assert!(switch_branch(&repository.0, "main").is_ok());
assert_eq!(get_current_branch2(&repository.1), String::from("main"));
assert!(switch_branch(&repository.0, "main").is_ok());
assert_eq!(get_current_branch2(&repository.1), String::from("main"));
assert!(switch_branch(&repository.0, "branch1").is_ok());
assert_eq!(get_current_branch2(&repository.1), String::from("branch1"));
assert!(switch_branch(&repository.0, "branch3").is_ok());
assert_eq!(get_current_branch2(&repository.1), String::from("branch3"));
}
#[rstest]
fn test_get_current_branch(repository: (Repository, PathBuf)) {
create_branches(&repository.1);
assert_eq!(
get_current_branch(&repository.0).unwrap(),
String::from("main")
);
switch_branch2(&repository.1, "branch1");
assert_eq!(
get_current_branch(&repository.0).unwrap(),
String::from("branch1")
);
switch_branch2(&repository.1, "main");
assert_eq!(
get_current_branch(&repository.0).unwrap(),
String::from("main")
);
switch_branch2(&repository.1, "branch1");
assert_eq!(
get_current_branch(&repository.0).unwrap(),
String::from("branch1")
);
switch_branch2(&repository.1, "branch3");
assert_eq!(
get_current_branch(&repository.0).unwrap(),
String::from("branch3")
);
}
#[rstest]
fn test_branch(repository: (Repository, PathBuf)) {
create_branches(&repository.1);
let mut branches = branch(&repository.0)
.unwrap()
.filter_map(|b| b.ok())
.map(|(branch, _)| branch.name().unwrap().unwrap().to_string())
.collect::<Vec<String>>();
branches.sort();
let expected = vec![
String::from("branch1"),
String::from("branch2"),
String::from("branch3"),
String::from("main"),
];
assert_eq!(branches, expected);
}
#[rstest]
#[case("main", true)]
#[case("branch1", true)]
#[case("branch3", true)]
#[case("master", false)]
#[case("invalid", false)]
fn test_exists_branch(
repository: (Repository, PathBuf),
#[case] branch: &str,
#[case] expected: bool,
) {
create_branches(&repository.1);
assert_eq!(exists_branch(&repository.0, branch), expected);
}
#[rstest]
fn test_is_dirty(repository: (Repository, PathBuf)) {
// Clean Repo
assert!(!is_dirty(&repository.1).unwrap());
// Untracked file
let mut file = repository.1.to_owned();
file.push("FILE1");
fs::write(&file, "bla").unwrap();
assert!(is_dirty(&repository.1).unwrap());
// Staged file
Command::new("git")
.arg("add")
.arg("FILE1")
.current_dir(&repository.1)
.output()
.unwrap();
assert!(is_dirty(&repository.1).unwrap());
// Clean Repo
Command::new("git")
.arg("commit")
.arg("--message")
.arg("Add FILE1")
.current_dir(&repository.1)
.output()
.unwrap();
assert!(!is_dirty(&repository.1).unwrap());
// Unstaged Changes
fs::write(&file, "new bla").unwrap();
assert!(is_dirty(&repository.1).unwrap());
// Staged changes
Command::new("git")
.arg("add")
.arg("FILE1")
.current_dir(&repository.1)
.output()
.unwrap();
assert!(is_dirty(&repository.1).unwrap());
// Clean Repo
Command::new("git")
.arg("commit")
.arg("--message")
.arg("Update FILE1")
.current_dir(&repository.1)
.output()
.unwrap();
assert!(!is_dirty(&repository.1).unwrap());
}
// fn test_run() {}
// fn test_show() {}
}

View File

@ -1,5 +1,6 @@
#![feature(absolute_path)]
#![feature(let_chains)] // git::switch_branch
#![feature(assert_matches)] // replaces `assert!(matches!(x, y))` in tests
mod archive;
mod commands;
mod config;
@ -8,6 +9,9 @@ mod format_string;
mod git;
mod metadata;
#[cfg(test)]
mod conftest;
use anyhow::Result;
use clap::Parser;