diff --git a/.gitignore b/.gitignore index 47815fde675..747d4b3620f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ perl/Makefile.config /src/libexpr/nix.tbl /src/libexpr/tests/libnixexpr-tests +# /src/libfetchers/ +/src/libfetchers/tests/libnixfetchers-tests + # /src/libstore/ *.gen.* /src/libstore/tests/libnixstore-tests diff --git a/Makefile b/Makefile index 42d11638b19..1f4d99f8163 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ makefiles = \ src/libstore/local.mk \ src/libstore/tests/local.mk \ src/libfetchers/local.mk \ + src/libfetchers/tests/local.mk \ src/libmain/local.mk \ src/libexpr/local.mk \ src/libexpr/tests/local.mk \ diff --git a/src/libfetchers/tests/git.cc b/src/libfetchers/tests/git.cc new file mode 100644 index 00000000000..dab86f4d7d9 --- /dev/null +++ b/src/libfetchers/tests/git.cc @@ -0,0 +1,204 @@ +#include "fetchers.hh" +#include "input-accessor.hh" +#include "store-api.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace nix; +using namespace nix::fetchers; + +using testing::SizeIs; + +class TmpDir { +public: + const nix::Path dir; +private: + AutoDelete autoDelete; + +public: + TmpDir() : dir(createTempDir()), autoDelete(dir) { } +}; + +class TmpStore : public TmpDir { + ref store; +public: + TmpStore() : store(openStore(dir)) { } + + operator ref() { + return store; + } +}; + +class TmpRepo : public TmpDir { + AutoDelete autoDelete; + +public: + git_repository *repo = NULL; + + TmpRepo(bool isBare = false) + { + git_libgit2_init(); + if (git_repository_init(&repo, dir.c_str(), isBare)) + throw Error("creating Git repository '%s': %s", dir, git_error_last()->message); + } + ~TmpRepo() { + git_repository_free(repo); + git_libgit2_shutdown(); + } + + // For troubleshooting a test + void dump() { + (void) system((std::string("exec 2>&1; cd '") + dir + "'; (set -eux; ls -alR; git reflog; git log; git log --graph --oneline --branches --all --color=always; git status;) | cat").c_str()); + } +}; + +static void commitIndexNoParent(git_repository *repo, git_oid &tree_id, git_oid &commit_id) { + git_signature *sig; + git_tree *tree; + + git_signature_now(&sig, "Test User", "test@example.com"); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + throw Error("Could not look up initial tree: %s", git_error_last()->message); + + if (git_commit_create_v( + &commit_id, repo, "HEAD", sig, sig, + NULL, "Initial commit", tree, 0) < 0) + throw Error("Could not create the initial commit: %s", git_error_last()->message); + + git_tree_free(tree); + git_signature_free(sig); +} + +static void initCommit(git_repository *repo) { + git_index *index; + git_oid tree_id, commit_id; + + if (git_repository_index(&index, repo) < 0) + throw Error("Could not open repository index: %s", git_error_last()->message); + + if (git_index_write_tree(&tree_id, index) < 0) + throw Error("Unable to write initial tree from index: %s", git_error_last()->message); + + git_index_free(index); + + commitIndexNoParent(repo, tree_id, commit_id); +} + +// I think an empty source would be ok? +TEST(git, when_the_repo_and_worktree_are_empty__fetchTree_on_the_worktree_returns_an_error) { + TmpRepo repo; + TmpStore store; + + Attrs attrs; + attrs.emplace("type", "git"); + attrs.emplace("url", std::string("file://") + repo.dir); + + auto input = Input::fromAttrs(std::move(attrs)); + auto r = input.getAccessor(store); + + InputAccessor &accessor = *r.first; + + ASSERT_THROW({ + auto root = accessor.root(); + auto dir = root.readDirectory(); + }, RestrictedPathError); +} + +// I think an empty source would be ok? +TEST(git, when_the_repo_head_and_worktree_are_empty__fetchTree_on_the_worktree_returns_an_error) { + TmpRepo repo; + TmpStore store; + + initCommit(repo.repo); + + Attrs attrs; + attrs.emplace("type", "git"); + attrs.emplace("url", std::string("file://") + repo.dir); + + auto input = Input::fromAttrs(std::move(attrs)); + auto r = input.getAccessor(store); + + InputAccessor &accessor = *r.first; + + ASSERT_THROW({ + auto root = accessor.root(); + auto dir = root.readDirectory(); + }, RestrictedPathError); +} + +TEST(git, when_the_repo_head_exists_empty_and_worktree_are_empty__fetchTree_on_the_head_returns_an_empty_tree) { + TmpRepo repo; + TmpStore store; + + initCommit(repo.repo); + + Attrs attrs; + attrs.emplace("type", "git"); + attrs.emplace("ref", "HEAD"); + attrs.emplace("url", std::string("file://") + repo.dir); + + auto input = Input::fromAttrs(std::move(attrs)); + auto r = input.getAccessor(store); + + InputAccessor &accessor = *r.first; + + auto root = accessor.root(); + auto dir = root.readDirectory(); + ASSERT_THAT(dir, SizeIs(0)); + + auto fetched = input.fetchToStore(store); + ASSERT_EQ(fetched.first.to_string(), "0ccnxa25whszw7mgbgyzdm4nqc0zwnm8-source"); +} + +TEST(git, when_the_head_contains_a_file_and_head_is_requested_return_the_file) { + TmpRepo repo; + TmpStore store; + + + git_index *index; + git_oid tree_id, commit_id; + + if (git_repository_index(&index, repo.repo) < 0) + throw Error("Could not open repository index: %s", git_error_last()->message); + + git_index_entry index_entry; + memset(&index_entry, 0, sizeof(index_entry)); + index_entry.path = "hello"; + index_entry.mode = GIT_FILEMODE_BLOB; + + if (git_index_add_from_buffer(index, &index_entry, "hi", 2)) + throw Error("Could not add to index: %s", git_error_last()->message); + + if (git_index_write_tree(&tree_id, index) < 0) + throw Error("Unable to write initial tree from index: %s", git_error_last()->message); + + git_index_free(index); + + commitIndexNoParent(repo.repo, tree_id, commit_id); + + Attrs attrs; + attrs.emplace("type", "git"); + attrs.emplace("ref", "HEAD"); + attrs.emplace("url", std::string("file://") + repo.dir); + + auto input = Input::fromAttrs(std::move(attrs)); + auto r = input.getAccessor(store); + + InputAccessor &accessor = *r.first; + + auto root = accessor.root(); + auto dir = root.readDirectory(); + + ASSERT_THAT(dir, SizeIs(1)); + + auto fetched = input.fetchToStore(store); + ASSERT_EQ(fetched.first.to_string(), "5rbhbvyxfxgc6ac9f65qqa7q193qanls-source"); +} diff --git a/src/libfetchers/tests/local.mk b/src/libfetchers/tests/local.mk new file mode 100644 index 00000000000..79a777f7dae --- /dev/null +++ b/src/libfetchers/tests/local.mk @@ -0,0 +1,18 @@ +check: libfetchers-tests_RUN + +programs += libfetchers-tests + +libfetchers-tests_NAME := libnixfetchers-tests + +libfetchers-tests_DIR := $(d) + +libfetchers-tests_INSTALL_DIR := + +libfetchers-tests_SOURCES := \ + $(wildcard $(d)/*.cc) + +libfetchers-tests_CXXFLAGS += -I src/libfetchers -I src/libutil -I src/libstore -I src/libfetchers/tests + +libfetchers-tests_LIBS = libstore-tests libutils-tests libfetchers libutil libstore + +libfetchers-tests_LDFLAGS := $(GTEST_LIBS) -lgmock -lgit2