1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
|
// 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 <algorithm> // std::find
#include <atomic>
#include <cstddef>
#include <cstdlib> // std::system
#include <filesystem>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <vector>
#include "catch2/catch_test_macros.hpp"
#include "fmt/core.h"
#include "src/buildtool/execution_api/common/ids.hpp"
#include "src/buildtool/file_system/file_system_manager.hpp"
#include "src/buildtool/multithreading/task_system.hpp"
#include "src/other_tools/git_operations/git_ops_types.hpp"
#include "src/other_tools/ops_maps/critical_git_op_map.hpp"
#include "test/utils/shell_quoting.hpp"
namespace {
auto const kBundlePath =
std::string{"test/buildtool/file_system/data/test_repo_symlinks.bundle"};
auto const kRootCommit =
std::string{"3ecce3f5b19ad7941c6354d65d841590662f33ef"};
auto const kBazSymId = std::string{"1868f82682c290f0b1db3cacd092727eef1fa57f"};
} // 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",
QuoteForShell(is_bare ? repo_path->string()
: (*repo_path / ".git").string()),
QuoteForShell(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 " : "",
QuoteForShell(kBundlePath),
QuoteForShell(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("Critical 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!
// 0. 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));
// 1 & 2. Initializing repos -> need only the paths
auto path_init_bare = TestUtilsMP::GetRepoPath(*prefix);
auto path_init_non_bare = TestUtilsMP::GetRepoPath(*prefix);
// 3. Tag a commit -> needs a repo with a commit
auto path_keep_tag = TestUtilsMP::CreateTestRepo(*prefix, true);
REQUIRE(path_keep_tag);
// 4. Get head commit -> needs a repo with HEAD ref available
auto path_get_head_id = TestUtilsMP::CreateTestRepoWithCheckout(*prefix);
REQUIRE(path_get_head_id);
// 5. Tag a tree -> needs a repo with a tree
auto path_keep_tree = TestUtilsMP::CreateTestRepo(*prefix, true);
REQUIRE(path_keep_tree);
// 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
constexpr auto kNumMethods = 6;
std::vector<std::size_t> ops_all(kNumMethods); // indices of all ops tested
std::iota(ops_all.begin(), ops_all.end(), 0);
const std::vector<std::size_t> ops_with_result{
0, 4}; // indices of ops that return a non-empty string
// Add to the map all ops multiple times
constexpr auto kRepeats = 3;
for ([[maybe_unused]] auto k = kRepeats; k > 0; --k) {
auto error = false;
auto error_msg = std::string("NONE");
{
TaskSystem ts;
for ([[maybe_unused]] auto j = kRepeats; j > 0; --j) {
crit_op_map.ConsumeAfterKeysReady(
&ts,
{GitOpKey{.params =
{
path_init_commit, // target_path
"", // git_hash
"Init commit", // message
path_init_commit // source_path
},
.op_type = GitOpType::INITIAL_COMMIT},
GitOpKey{.params =
{
path_init_bare, // target_path
"", // git_hash
std::nullopt, // message
std::nullopt, // source_path
true // init_bare
},
.op_type = GitOpType::ENSURE_INIT},
GitOpKey{.params =
{
path_init_non_bare, // target_path
"", // git_hash
std::nullopt, // message
std::nullopt, // source_path
false // init_bare
},
.op_type = GitOpType::ENSURE_INIT},
GitOpKey{.params =
{
*path_keep_tag, // target_path
kRootCommit, // git_hash
"keep-commit" // message
},
.op_type = GitOpType::KEEP_TAG},
GitOpKey{.params =
{
*path_get_head_id, // target_path
"", // git_hash
},
.op_type = GitOpType::GET_HEAD_ID},
GitOpKey{.params =
{
*path_keep_tree, // target_path
kBazSymId, // git_hash
"keep-tree" // message
},
.op_type = GitOpType::KEEP_TREE}},
[&ops_all, &ops_with_result](auto const& values) {
// check operations
for (std::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");
}
}
|