diff options
author | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2022-12-12 14:21:17 +0100 |
---|---|---|
committer | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2022-12-21 14:44:06 +0100 |
commit | ecff253633ada6ca88db8ab207c593ebde033044 (patch) | |
tree | 0b28307ef32998721c7b311467a8fdd0fa653d59 /test | |
parent | 05ad8569b852ffb61d9cb6d92efc9e867c083d1e (diff) | |
download | justbuild-ecff253633ada6ca88db8ab207c593ebde033044.tar.gz |
Test: Add tests for critical git ops
Diffstat (limited to 'test')
-rw-r--r-- | test/TARGETS | 1 | ||||
-rw-r--r-- | test/other_tools/TARGETS | 6 | ||||
-rw-r--r-- | test/other_tools/git_operations/TARGETS | 27 | ||||
-rw-r--r-- | test/other_tools/git_operations/critical_git_ops.test.cpp | 220 | ||||
-rw-r--r-- | test/other_tools/git_operations/critical_git_ops_mp.sh | 45 |
5 files changed, 299 insertions, 0 deletions
diff --git a/test/TARGETS b/test/TARGETS index 6c1c62e8..d178a3fb 100644 --- a/test/TARGETS +++ b/test/TARGETS @@ -12,6 +12,7 @@ [ [["./", "buildtool", "TESTS"], "buildtool"] , [["./", "utils", "TESTS"], "utils"] , [["./", "end-to-end", "TESTS"], "end-to-end"] + , [["./", "other_tools", "TESTS"], "other_tools"] ] } , "ALL": diff --git a/test/other_tools/TARGETS b/test/other_tools/TARGETS new file mode 100644 index 00000000..305c34af --- /dev/null +++ b/test/other_tools/TARGETS @@ -0,0 +1,6 @@ +{ "TESTS": + { "type": "install" + , "tainted": ["test"] + , "dirs": [[["./", "git_operations", "TESTS"], "git_operations"]] + } +} diff --git a/test/other_tools/git_operations/TARGETS b/test/other_tools/git_operations/TARGETS new file mode 100644 index 00000000..579bffdd --- /dev/null +++ b/test/other_tools/git_operations/TARGETS @@ -0,0 +1,27 @@ +{ "critical_git_ops_test_install": + { "type": ["@", "rules", "CC", "binary"] + , "tainted": ["test"] + , "name": ["critical_git_ops_test"] + , "srcs": ["critical_git_ops.test.cpp"] + , "private-deps": + [ ["@", "catch2", "", "catch2"] + , ["test", "catch-main"] + , ["@", "json", "", "json"] + , ["src/buildtool/file_system", "file_system_manager"] + , ["src/other_tools/ops_maps", "critical_git_op_map"] + , ["src/buildtool/execution_api/common", "common"] + ] + , "stage": ["test", "other_tools", "git_operations"] + } +, "critical_git_ops_mp": + { "type": ["@", "rules", "shell/test", "script"] + , "name": ["critical_git_ops_mp"] + , "test": ["critical_git_ops_mp.sh"] + , "deps": + [ ["test/buildtool/file_system", "test_data"] + , "critical_git_ops_test_install" + ] + } +, "TESTS": + {"type": "install", "tainted": ["test"], "deps": ["critical_git_ops_mp"]} +} 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 <thread> + +#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<std::filesystem::path> { + 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<std::filesystem::path> { + 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<std::filesystem::path> { + auto repo_path = GetRepoPath(prefix); + std::optional<std::filesystem::path> 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<int> 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<CriticalGitOpGuard>(); + 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<size_t> ops_all{ + 0, 1, 2, 3, 4, 5}; // indices of all ops tested + const std::vector<size_t> 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"); + } +} diff --git a/test/other_tools/git_operations/critical_git_ops_mp.sh b/test/other_tools/git_operations/critical_git_ops_mp.sh new file mode 100644 index 00000000..1be2c656 --- /dev/null +++ b/test/other_tools/git_operations/critical_git_ops_mp.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# 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. + +set -eu + +# Run the critical git ops test multiple times from different processes +echo "running critical git ops test from 4 processes" +error=false +test/other_tools/git_operations/critical_git_ops_test & res1=$! +test/other_tools/git_operations/critical_git_ops_test & res2=$! +test/other_tools/git_operations/critical_git_ops_test & res3=$! +test/other_tools/git_operations/critical_git_ops_test & res4=$! +wait $res1 +if [ $? -ne 0 ]; then + error=true +fi +wait $res2 +if [ $? -ne 0 ]; then + error=true +fi +wait $res3 +if [ $? -ne 0 ]; then + error=true +fi +wait $res4 +if [ $? -ne 0 ]; then + error=true +fi +# if one fails, set fail overall +if [ $error = true ]; then + exit 1 +fi +echo "done" |