// 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); } }