From ecff253633ada6ca88db8ab207c593ebde033044 Mon Sep 17 00:00:00 2001 From: Paul Cristian Sarbu Date: Mon, 12 Dec 2022 14:21:17 +0100 Subject: Test: Add tests for critical git ops --- .../git_operations/critical_git_ops.test.cpp | 220 +++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 test/other_tools/git_operations/critical_git_ops.test.cpp (limited to 'test/other_tools/git_operations/critical_git_ops.test.cpp') diff --git a/test/other_tools/git_operations/critical_git_ops.test.cpp b/test/other_tools/git_operations/critical_git_ops.test.cpp new file mode 100644 index 00000000..7c74d352 --- /dev/null +++ b/test/other_tools/git_operations/critical_git_ops.test.cpp @@ -0,0 +1,220 @@ +// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "catch2/catch.hpp" +#include "nlohmann/json.hpp" +#include "src/buildtool/execution_api/common/execution_common.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/other_tools/ops_maps/critical_git_op_map.hpp" + +namespace { + +auto const kBundlePath = + std::string{"test/buildtool/file_system/data/test_repo.bundle"}; +auto const kRootCommit = + std::string{"bc5f88b46bbf0c4c61da7a1296fa9a0559b92822"}; + +} // namespace + +/// \brief TestUtils that accounts for multi-process calls. +/// Ensures the git clone only happens once per path. +/// Can also create process-unique paths. +class TestUtilsMP { + public: + [[nodiscard]] static auto GetUniqueTestDir() noexcept + -> std::optional { + auto* tmp_dir = std::getenv("TEST_TMPDIR"); + if (tmp_dir != nullptr) { + return CreateUniquePath(tmp_dir); + } + return CreateUniquePath(FileSystemManager::GetCurrentDirectory() / + "test/other_tools"); + } + + [[nodiscard]] static auto GetRepoPath( + std::filesystem::path const& prefix) noexcept -> std::filesystem::path { + return prefix / "test_git_repo" / + std::filesystem::path{std::to_string(counter++)}.filename(); + } + + [[nodiscard]] static auto GetRepoPathUnique( + std::filesystem::path const& prefix) noexcept -> std::filesystem::path { + return prefix / ("test_git_repo." + CreateProcessUniqueId().value()) / + std::filesystem::path{std::to_string(counter++)}.filename(); + } + + // The checkout will make the content available, as well as the HEAD ref + [[nodiscard]] static auto CreateTestRepoWithCheckout( + std::filesystem::path const& prefix, + bool is_bare = false) noexcept -> std::optional { + auto repo_path = CreateTestRepo(prefix, is_bare); + REQUIRE(repo_path); + auto cmd = fmt::format( + "git --git-dir={} --work-tree={} checkout master", + is_bare ? repo_path->string() : (*repo_path / ".git").string(), + repo_path->string()); + if (std::system(cmd.c_str()) == 0) { + return *repo_path; + } + return std::nullopt; + } + + [[nodiscard]] static auto CreateTestRepo( + std::filesystem::path const& prefix, + bool is_bare = false) noexcept -> std::optional { + auto repo_path = GetRepoPath(prefix); + std::optional result = std::nullopt; + // only do work if another process hasn't already been here + if (not FileSystemManager::Exists(repo_path)) { + auto cmd = fmt::format("git clone {}{} {}", + is_bare ? "--bare " : "", + kBundlePath, + repo_path.string()); + if (std::system(cmd.c_str()) == 0) { + result = repo_path; + } + } + else { + result = repo_path; + } + return result; + } + + private: + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + static inline std::atomic counter = 0; +}; + +TEST_CASE("Crtitical git operations", "[critical_git_op_map]") { + // setup the repos needed + auto prefix = TestUtilsMP::GetUniqueTestDir(); + REQUIRE(prefix); + + auto testdir = *prefix / "test_git_repo"; + REQUIRE(FileSystemManager::CreateDirectory(testdir)); + + // create the remote for the fetch ops + auto remote_repo_path = TestUtilsMP::CreateTestRepoWithCheckout(*prefix); + REQUIRE(remote_repo_path); + + // create the target paths for the various critical ops + // IMPORTANT! For non-init critical ops the paths need to exist already! + // 1. Initial commit -> needs a path containing some files + // This has to be process unique, as the commit will fail otherwise! + auto path_init_commit = TestUtilsMP::GetRepoPathUnique(*prefix); + REQUIRE(FileSystemManager::WriteFile( + "test no 1", path_init_commit / "test1.txt", true)); + REQUIRE(FileSystemManager::WriteFile( + "test no 2", path_init_commit / "test2.txt", true)); + // 2 & 3. Initializing repos -> need only the paths + auto path_init_bare = TestUtilsMP::GetRepoPath(*prefix); + auto path_init_non_bare = TestUtilsMP::GetRepoPath(*prefix); + // 4. Tag a commit -> needs a repo with a commit + auto path_keep_tag = TestUtilsMP::CreateTestRepo(*prefix, true); + REQUIRE(path_keep_tag); + // 5. Get head commit -> needs a repo with a commit + auto path_get_head_id = TestUtilsMP::CreateTestRepoWithCheckout(*prefix); + REQUIRE(path_get_head_id); + // 6. Get local branch refname -> needs a repo with a commit + auto path_get_branch_refname = + TestUtilsMP::CreateTestRepoWithCheckout(*prefix); + REQUIRE(path_get_branch_refname); + + // create the map + auto crit_op_guard = std::make_shared(); + auto crit_op_map = CreateCriticalGitOpMap(crit_op_guard); + + // Add ops to the map. None should throw, as repeating the same operation + // should retrieve the value from the map, not call the operation again. + // helper lists + const std::vector ops_all{ + 0, 1, 2, 3, 4, 5}; // indices of all ops tested + const std::vector ops_with_result{ + 0, 4, 5}; // indices of ops that return a non-empty string + // Add to the map all ops multiple times + for ([[maybe_unused]] auto const& i : + {0, 0, 0}) { // replace once ranges are available + // (https://en.cppreference.com/w/cpp/ranges/iota_view) + auto error = false; + auto error_msg = std::string("NONE"); + { + TaskSystem ts; + crit_op_map.ConsumeAfterKeysReady( + &ts, + {GitOpKey{GitOpParams{ + path_init_commit, // target_path + "", // git_hash + "", // branch + "Init commit" // message + }, + GitOpType::INITIAL_COMMIT}, + GitOpKey{GitOpParams{ + path_init_bare, // target_path + "", // git_hash + "", // branch + std::nullopt, // message + true // init_bare + }, + GitOpType::ENSURE_INIT}, + GitOpKey{GitOpParams{ + path_init_non_bare, // target_path + "", // git_hash + "", // branch + std::nullopt, // message + false // init_bare + }, + GitOpType::ENSURE_INIT}, + GitOpKey{GitOpParams{ + *path_keep_tag, // target_path + kRootCommit, // git_hash + "", // branch + "keep-me" // message + }, + GitOpType::KEEP_TAG}, + GitOpKey{GitOpParams{ + *path_get_head_id, // target_path + "", // git_hash + "", // branch + }, + GitOpType::GET_HEAD_ID}, + GitOpKey{GitOpParams{ + *path_get_branch_refname, // target_path + "", // git_hash + "master", // branch + }, + GitOpType::GET_BRANCH_REFNAME}}, + [&ops_all, &ops_with_result](auto const& values) { + // check operations + for (size_t const& i : ops_all) { + auto res = *values[i]; + REQUIRE(res.git_cas); + REQUIRE(res.result); + if (std::find(ops_with_result.begin(), + ops_with_result.end(), + i) != ops_with_result.end()) { + CHECK(not res.result->empty()); + } + } + }, + [&error, &error_msg](std::string const& msg, bool /*unused*/) { + error = true; + error_msg = msg; + }); + } + CHECK_FALSE(error); + CHECK(error_msg == "NONE"); + } +} -- cgit v1.2.3