// This file is part of GAnarchy - decentralized development hub
// Copyright (C) 2021 Soni L.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//! Unit tests for the git module.
use std::panic::AssertUnwindSafe;
use assert_fs::assert::PathAssert;
use assert_fs::fixture::{TempDir, PathChild};
use predicates;
use testserver::serve;
use crate::marker::Initializer;
use super::{Git, FetchRepo, CacheRepo};
const MAGIC_BRANCH: &'static str = "gan\
0000000000000000000000000000000000000000000000000000000000000000";
const MAGIC_COMMIT: &'static str = "9d7224353c34ad675ee5e43fb3115aaaf98832e9";
// Oh yes.
static REPO: &'static str =
r#####"!
branches.a/ 0 0 0 644 8 `
!
config.t/ 0 0 0 644 66 `
[core]
repositoryformatversion = 0
filemode = true
bare = true
description.t/ 0 0 0 644 73 `
Unnamed repository; edit this file 'description' to name the repository.
HEAD.t/ 0 0 0 644 24 `
ref: refs/heads/default
hooks.a/ 0 0 0 644 8 `
!
info.a/ 0 0 0 644 428 `
!
exclude.t/ 0 0 0 644 240 `
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
refs.t/ 0 0 0 644 60 `
9d7224353c34ad675ee5e43fb3115aaaf98832e9 refs/heads/default
objects.a/ 0 0 0 644 916 `
!
4b.a/ 0 0 0 644 192 `
!
// 42 `
825dc642cb6eb9a060e54bf8d69288fbee4904.b/
/0 0 0 0 644 21 `
eAErKUpNVTBgAAAKLAIB
9d.a/ 0 0 0 644 398 `
!
// 42 `
7224353c34ad675ee5e43fb3115aaaf98832e9.b/
/0 0 0 0 644 227 `
eAGdjUsKwjAURR1nFW8DlpikIQURHRSngs7EQT6vNtIkJUZod2/BrkC4o3M5HJtC8AUYp5uSEUEY
xWpnpWDWSDSNppJiLUynnGyYUp1BFA0VRH9KnzJcU/TtxGCP0WEOKeIckjs+g/ZDZVM4wE4yXnMm
dw1sKaeULHRJFvxLJvdLTi+05QHtpMM4IKyAkFvv37BMR8D1O5+izrafYfxZFfkCj/1MqQ==
info.a/ 0 0 0 644 70 `
!
packs.t/ 0 0 0 644 1 `
pack.a/ 0 0 0 644 8 `
!
refs.a/ 0 0 0 644 246 `
!
heads.a/ 0 0 0 644 110 `
!
default.t/ 0 0 0 644 41 `
9d7224353c34ad675ee5e43fb3115aaaf98832e9
tags.a/ 0 0 0 644 8 `
!
"#####;
/// Tests the low-level (raw) commands.
mod low_level {
use super::*;
/// Tests that cmd_init works.
#[test]
fn test_cmd_git_init() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let git = Git::at_path(Initializer, repo.path()).unwrap();
git.cmd_init(|args| {
// templates actually override the -b flag, but at least
// this tests that passing args to git init actually works.
// also note that the -b flag requires a fairly recent version
// of git but ah well. .-.
args.arg("-b").arg("hello");
}).unwrap();
// git init *always* creates HEAD, even for bare repos!
// we don't actually care about what HEAD contains tho.
repo.child("HEAD").assert(predicates::path::exists());
}
/// Tests that cmd_clone works.
#[test]
fn test_cmd_git_clone() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let git = Git::at_path(Initializer, repo.path()).unwrap();
git.cmd_init(|_| {}).unwrap();
// check that a HEAD exists
repo.child("HEAD").assert(predicates::path::exists());
let clone = dir.child("ganarchy-fetch-0.git");
// have to create a Git manually for this one. ah well. :(
let git_clone = Git {
path: clone.path().into(),
inner: CacheRepo {
_non_exhaustive: (),
},
sha256: false,
};
git_clone.cmd_clone_from(repo.path(), |args| {
// also test if git clone args work.
args.arg("--shared");
}).unwrap();
// git clone should carry over the HEAD
let pred = predicates::path::eq_file(repo.child("HEAD").path());
clone.child("HEAD").assert(pred);
}
/// Tests that cmd works and that we can get output from it.
#[test]
fn test_cmd() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let git = Git::at_path(Initializer, repo.path()).unwrap();
// we do need to init this because git attempts to cd to it.
git.cmd_init(|_| {}).unwrap();
let output = git.cmd(|args| {
args.arg("check-ref-format");
args.arg("--normalize");
args.arg("//refs/heads/headache");
}).unwrap();
assert_eq!(output.stdout, b"refs/heads/headache\n");
}
}
/// Tests private operations.
mod privates {
use super::*;
/// Tests that fork works.
#[test]
fn test_fork() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let clone = dir.child("ganarchy-fetch-0.git");
// have to create a Git manually for this one. ah well. :(
let mut git_clone = Git {
path: clone.path().into(),
inner: FetchRepo {
pending_branches: Default::default(),
},
sha256: false,
};
git.fork(&mut git_clone).unwrap();
}
/// Tests that delete works.
#[test]
fn test_delete() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let clone = dir.child("ganarchy-fetch-0.git");
// have to create a Git manually for this one. ah well. :(
let mut git_clone = Git {
path: clone.path().into(),
inner: FetchRepo {
pending_branches: Default::default(),
},
sha256: false,
};
git.fork(&mut git_clone).unwrap();
git_clone.delete().unwrap();
}
// TODO rm_branch, replace, fetch_work, etc!
// (or we can rely on with_work_repos as an integration test)
}
/// Tests public operations.
mod publics {
use super::*;
/// Tests that ensure_exists works.
#[test]
fn test_ensure_exists() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
}
/// Tests that with_work_repos works.
#[test]
fn test_with_work_repos() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")
}).unwrap();
git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
// This is basically check_history, but we skip cache repo check.
repo.cmd(|args| {
args.arg("merge-base");
args.arg("--is-ancestor");
args.arg(MAGIC_COMMIT);
args.arg(format!("refs/heads/{}", MAGIC_BRANCH));
})
}).unwrap();
}
/// Tests that fetch_source works.
#[test]
fn test_fetch_source() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")
}).unwrap();
}
/// Tests that with_work_repos works properly on failure.
#[test]
fn test_with_work_repos_failure() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
let addr2 = format!("{}{}", &addr, "nonexistent");
let res = git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD").unwrap();
repo.fetch_source(&addr2, MAGIC_BRANCH, "HEAD")
});
assert!(res.is_err());
// it should not merge the successful one.
assert!(git.check_history(MAGIC_BRANCH, MAGIC_COMMIT).is_err());
}
/// Tests that panicking through with_work_repos leaves the disk "dirty".
/// Also tests that a future call to with_work_repos fails gracefully.
#[test]
fn test_with_work_repos_panic() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
// we DO NOT want to call unwrap() on ANY of these.
git.with_work_repos::<_, ()>(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")?;
panic!()
})
}));
// check that it panicked.
assert!(res.is_err());
// now check that future calls return an error, without calling the
// closure.
let res: Result<(), _> = git.with_work_repos(1, |_| panic!());
assert!(res.is_err());
}
/// Tests that check_branch is correct.
#[test]
fn test_check_branch() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
git.check_branch("default").unwrap();
assert!(git.check_branch("HEAD").is_err());
}
/// Tests that check_history is correct.
#[test]
fn test_check_history() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")
}).unwrap();
git.check_history(MAGIC_BRANCH, MAGIC_COMMIT).unwrap();
}
/// Tests that get_counts is correct. (non-exhaustive)
#[test]
fn test_get_counts() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
let counts = git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")?;
repo.get_counts(MAGIC_COMMIT, MAGIC_COMMIT)
}).unwrap();
// Unfortunately we can only check MAGIC_COMMIT...MAGIC_COMMIT,
// so this is always gonna be (0, 0).
assert_eq!(counts, (0, 0));
}
/// Tests that get_hash is correct.
#[test]
fn test_get_hash() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
let hash = git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")?;
repo.get_hash(MAGIC_BRANCH)
}).unwrap();
let hashtoo = git.get_hash(MAGIC_BRANCH).unwrap();
assert_eq!(hash, MAGIC_COMMIT);
assert_eq!(hash, hashtoo);
}
/// Tests that get_message is correct.
#[test]
fn test_get_message() {
let dir = TempDir::new().unwrap();
let repo = dir.child("ganarchy-cache.git");
let mut git = Git::at_path(Initializer, repo.path()).unwrap();
git.ensure_exists().unwrap();
let server = serve(REPO);
let addr = format!("http://{}:{}/", testserver::IP, server.port());
let msg = git.with_work_repos(1, |repos| {
let repo = &mut repos[0];
repo.fetch_source(&addr, MAGIC_BRANCH, "HEAD")?;
repo.get_message(MAGIC_COMMIT)
}).unwrap();
let msgtoo = git.get_message(MAGIC_COMMIT).unwrap();
let expect = "[Project] Example Project\n\
\n\
This is an example GAnarchy project.\n";
assert_eq!(msg, expect);
assert_eq!(msg, msgtoo);
}
}