diff options
author | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2024-04-04 12:57:31 +0200 |
---|---|---|
committer | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2024-04-10 14:06:21 +0200 |
commit | 1b0aa19f20d102bf96ada7a1e77843eeef20985c (patch) | |
tree | a557bddd21d7482429563aeb2f1720902447920e | |
parent | 646d52aac3b5e14ae0c2a552987d84f184345178 (diff) | |
download | justbuild-1b0aa19f20d102bf96ada7a1e77843eeef20985c.tar.gz |
git_repo: Add blob writer method
Also extends the tests accordingly.
-rw-r--r-- | src/buildtool/file_system/git_repo.cpp | 32 | ||||
-rw-r--r-- | src/buildtool/file_system/git_repo.hpp | 9 | ||||
-rw-r--r-- | test/buildtool/file_system/git_repo.test.cpp | 313 |
3 files changed, 234 insertions, 120 deletions
diff --git a/src/buildtool/file_system/git_repo.cpp b/src/buildtool/file_system/git_repo.cpp index c0774971..c7ecddda 100644 --- a/src/buildtool/file_system/git_repo.cpp +++ b/src/buildtool/file_system/git_repo.cpp @@ -1433,6 +1433,38 @@ auto GitRepo::TryReadBlob(std::string const& blob_id, #endif // BOOTSTRAP_BUILD_TOOL } +auto GitRepo::WriteBlob(std::string const& content, + anon_logger_ptr const& logger) noexcept + -> std::optional<std::string> { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#else + try { + // preferably with a "fake" repository! + if (not IsRepoFake()) { + Logger::Log(LogLevel::Debug, + "Blob writer called on a real repository"); + } + // share the odb lock + std::shared_lock lock{GetGitCAS()->mutex_}; + + git_oid blob_oid; + if (git_blob_create_from_buffer( + &blob_oid, repo_->Ptr(), content.c_str(), content.size()) != + 0) { + (*logger)(fmt::format("writing blob into database failed with:\n{}", + GitLastError()), + /*fatal=*/true); + return std::nullopt; + } + return std::string{git_oid_tostr_s(&blob_oid)}; + } catch (std::exception const& ex) { + Logger::Log(LogLevel::Error, "write blob failed with:\n{}", ex.what()); + return std::nullopt; + } +#endif // BOOTSTRAP_BUILD_TOOL +} + auto GitRepo::GetObjectByPathFromTree(std::string const& tree_id, std::string const& rel_path) noexcept -> std::optional<TreeEntryInfo> { diff --git a/src/buildtool/file_system/git_repo.hpp b/src/buildtool/file_system/git_repo.hpp index 885ddde1..60353a00 100644 --- a/src/buildtool/file_system/git_repo.hpp +++ b/src/buildtool/file_system/git_repo.hpp @@ -263,6 +263,15 @@ class GitRepo { anon_logger_ptr const& logger) noexcept -> std::pair<bool, std::optional<std::string>>; + /// \brief Write given content as a blob into underlying object database. + /// Calling it from a fake repository allows thread-safe use. + /// \returns Git ID of the written blob, or nullopt on errors. + /// It guarantees the logger is called exactly once with fatal if failure. + /// Use with care, especially for large objects. + [[nodiscard]] auto WriteBlob(std::string const& content, + anon_logger_ptr const& logger) noexcept + -> std::optional<std::string>; + /// \brief Get the object info related to a given path inside a Git tree. /// Unlike GetSubtreeFromTree, we here ignore errors and only return a value /// when all is successful. diff --git a/test/buildtool/file_system/git_repo.test.cpp b/test/buildtool/file_system/git_repo.test.cpp index de8d0786..a6edfc8a 100644 --- a/test/buildtool/file_system/git_repo.test.cpp +++ b/test/buildtool/file_system/git_repo.test.cpp @@ -242,7 +242,182 @@ TEST_CASE("Single-threaded real repository local operations", "[git_repo]") { } } -TEST_CASE("Single-threaded fake repository operations", "[git_repo]") { +// NOTE: "fake" repo ops tests split into two batches as workaround for +// CATCH2_INTERNAL_TEST_16 function size/complexity threshold exceeding lint +// warning +TEST_CASE("Single-threaded fake repository operations -- batch 1", + "[git_repo]") { + auto repo_path = TestUtils::CreateTestRepoWithCheckout(); + REQUIRE(repo_path); + auto cas = GitCAS::Open(*repo_path); + REQUIRE(cas); + auto repo = GitRepo::Open(cas); + REQUIRE(repo); + REQUIRE(repo->GetGitCAS() == cas); + REQUIRE(repo->IsRepoFake()); + + // setup dummy logger + auto logger = std::make_shared<GitRepo::anon_logger_t>( + [](auto const& msg, bool fatal) { + Logger::Log(fatal ? LogLevel::Error : LogLevel::Progress, + std::string(msg)); + }); + + SECTION("Check tree exists") { + auto res = repo->CheckTreeExists(kRootId, logger); + REQUIRE(res); + CHECK(*res); + + res = repo->CheckTreeExists(kBazId, logger); + REQUIRE(res); + CHECK(*res); + + res = repo->CheckTreeExists(kFooId, logger); + REQUIRE(res); + CHECK_FALSE(*res); + } + + SECTION("Check blob exists") { + auto res = repo->CheckBlobExists(kFooId, logger); + REQUIRE(res); + CHECK(*res); + + res = repo->CheckBlobExists(kBarId, logger); + REQUIRE(res); + CHECK(*res); + + res = repo->CheckBlobExists(kBazId, logger); + REQUIRE(res); + CHECK_FALSE(*res); + } + + SECTION("Write and read blobs") { + SECTION("Existing blobs") { + auto res = repo->TryReadBlob(kFooId, logger); + REQUIRE(res.first); + REQUIRE(res.second); + CHECK(res.second == "foo"); + + res = repo->TryReadBlob(kBarId, logger); + REQUIRE(res.first); + REQUIRE(res.second); + CHECK(res.second == "bar"); + + res = repo->TryReadBlob(kBazId, logger); + REQUIRE(res.first); // search succeeded... + CHECK_FALSE(res.second); // ...but blob not found + } + + SECTION("New blobs in existing repo") { + auto w = repo->WriteBlob("foobar", logger); + REQUIRE(w); + auto blob_id = w.value(); + + auto r = repo->TryReadBlob(blob_id, logger); + REQUIRE(r.first); + REQUIRE(r.second); + CHECK(r.second == "foobar"); + } + + SECTION("Overwrite blob does not fail") { + auto w = repo->WriteBlob("foo", logger); + REQUIRE(w); + CHECK(w == kFooId); + } + + SECTION("New blobs in bare repo") { + // make blank repo + auto repo_path = TestUtils::GetRepoPath(); + auto repo = GitRepo::InitAndOpen(repo_path, /*is_bare=*/false); + REQUIRE(repo); + CHECK_FALSE(repo->IsRepoFake()); + + auto w = repo->WriteBlob("foobar", logger); + REQUIRE(w); + auto blob_id = w.value(); + + auto r = repo->TryReadBlob(blob_id, logger); + REQUIRE(r.first); + REQUIRE(r.second); + CHECK(r.second == "foobar"); + } + } + + SECTION("Check if commit exists in repository") { + SECTION("Repository containing commit") { + auto path_containing = TestUtils::CreateTestRepo(); + REQUIRE(path_containing); + auto cas_containing = GitCAS::Open(*path_containing); + REQUIRE(cas_containing); + auto repo_containing = GitRepo::Open(cas_containing); + REQUIRE(repo_containing); + + auto result_containing = + repo_containing->CheckCommitExists(kRootCommit, logger); + CHECK(*result_containing); + } + + SECTION("Repository not containing commit") { + auto path_non_bare = TestUtils::GetRepoPath(); + { + auto repo_tmp = + GitRepo::InitAndOpen(path_non_bare, /*is_bare=*/false); + REQUIRE(repo_tmp); + } + auto cas_non_bare = GitCAS::Open(path_non_bare); + REQUIRE(cas_non_bare); + auto repo_non_bare = GitRepo::Open(cas_non_bare); + REQUIRE(repo_non_bare); + + auto result_non_bare = + repo_non_bare->CheckCommitExists(kRootCommit, logger); + CHECK_FALSE(*result_non_bare); + } + } + + SECTION("Fetch from local repository via temporary repository") { + SECTION("Fetch all") { + // set repo to fetch into + auto path_fetch_all = TestUtils::GetRepoPath(); + auto repo_fetch_all = + GitRepo::InitAndOpen(path_fetch_all, /*is_bare=*/true); + REQUIRE(repo_fetch_all); + + // check commit is not there before fetch + CHECK_FALSE( + *repo_fetch_all->CheckCommitExists(kRootCommit, logger)); + + // fetch all with base refspecs + REQUIRE(repo_fetch_all->LocalFetchViaTmpRepo( + *repo_path, std::nullopt, logger)); + + // check commit is there after fetch + CHECK(*repo_fetch_all->CheckCommitExists(kRootCommit, logger)); + } + + SECTION("Fetch branch") { + // set repo to fetch into + auto path_fetch_branch = TestUtils::GetRepoPath(); + auto repo_fetch_branch = + GitRepo::InitAndOpen(path_fetch_branch, /*is_bare=*/true); + REQUIRE(repo_fetch_branch); + + // check commit is not there before fetch + CHECK_FALSE( + *repo_fetch_branch->CheckCommitExists(kRootCommit, logger)); + + // fetch branch + REQUIRE(repo_fetch_branch->LocalFetchViaTmpRepo( + *repo_path, "master", logger)); + + // check commit is there after fetch + CHECK(*repo_fetch_branch->CheckCommitExists(kRootCommit, logger)); + } + } +} + +TEST_CASE("Single-threaded fake repository operations -- batch 2", + "[git_repo]") { auto repo_path = TestUtils::CreateTestRepoWithCheckout(); REQUIRE(repo_path); auto cas = GitCAS::Open(*repo_path); @@ -326,50 +501,6 @@ TEST_CASE("Single-threaded fake repository operations", "[git_repo]") { } } - SECTION("Check tree exists") { - auto res = repo->CheckTreeExists(kRootId, logger); - REQUIRE(res); - CHECK(*res); - - res = repo->CheckTreeExists(kBazId, logger); - REQUIRE(res); - CHECK(*res); - - res = repo->CheckTreeExists(kFooId, logger); - REQUIRE(res); - CHECK_FALSE(*res); - } - - SECTION("Check blob exists") { - auto res = repo->CheckBlobExists(kFooId, logger); - REQUIRE(res); - CHECK(*res); - - res = repo->CheckBlobExists(kBarId, logger); - REQUIRE(res); - CHECK(*res); - - res = repo->CheckBlobExists(kBazId, logger); - REQUIRE(res); - CHECK_FALSE(*res); - } - - SECTION("Try read blob") { - auto res = repo->TryReadBlob(kFooId, logger); - REQUIRE(res.first); - REQUIRE(res.second); - CHECK(res.second == "foo"); - - res = repo->TryReadBlob(kBarId, logger); - REQUIRE(res.first); - REQUIRE(res.second); - CHECK(res.second == "bar"); - - res = repo->TryReadBlob(kBazId, logger); - REQUIRE(res.first); // search succeeded... - CHECK_FALSE(res.second); // ...but blob not found - } - SECTION("Get subtree entry id from path") { SECTION("Get base tree id") { auto entry_root_p = @@ -387,38 +518,6 @@ TEST_CASE("Single-threaded fake repository operations", "[git_repo]") { } } - SECTION("Check if commit exists in repository") { - SECTION("Repository containing commit") { - auto path_containing = TestUtils::CreateTestRepo(); - REQUIRE(path_containing); - auto cas_containing = GitCAS::Open(*path_containing); - REQUIRE(cas_containing); - auto repo_containing = GitRepo::Open(cas_containing); - REQUIRE(repo_containing); - - auto result_containing = - repo_containing->CheckCommitExists(kRootCommit, logger); - CHECK(*result_containing); - } - - SECTION("Repository not containing commit") { - auto path_non_bare = TestUtils::GetRepoPath(); - { - auto repo_tmp = - GitRepo::InitAndOpen(path_non_bare, /*is_bare=*/false); - REQUIRE(repo_tmp); - } - auto cas_non_bare = GitCAS::Open(path_non_bare); - REQUIRE(cas_non_bare); - auto repo_non_bare = GitRepo::Open(cas_non_bare); - REQUIRE(repo_non_bare); - - auto result_non_bare = - repo_non_bare->CheckCommitExists(kRootCommit, logger); - CHECK_FALSE(*result_non_bare); - } - } - SECTION("Read object from tree by relative path") { SECTION("Non-existing") { auto obj_info = @@ -448,46 +547,6 @@ TEST_CASE("Single-threaded fake repository operations", "[git_repo]") { CHECK(*obj_info->symlink_content == "bar"); } } - - SECTION("Fetch from local repository via temporary repository") { - SECTION("Fetch all") { - // set repo to fetch into - auto path_fetch_all = TestUtils::GetRepoPath(); - auto repo_fetch_all = - GitRepo::InitAndOpen(path_fetch_all, /*is_bare=*/true); - REQUIRE(repo_fetch_all); - - // check commit is not there before fetch - CHECK_FALSE( - *repo_fetch_all->CheckCommitExists(kRootCommit, logger)); - - // fetch all with base refspecs - REQUIRE(repo_fetch_all->LocalFetchViaTmpRepo( - *repo_path, std::nullopt, logger)); - - // check commit is there after fetch - CHECK(*repo_fetch_all->CheckCommitExists(kRootCommit, logger)); - } - - SECTION("Fetch branch") { - // set repo to fetch into - auto path_fetch_branch = TestUtils::GetRepoPath(); - auto repo_fetch_branch = - GitRepo::InitAndOpen(path_fetch_branch, /*is_bare=*/true); - REQUIRE(repo_fetch_branch); - - // check commit is not there before fetch - CHECK_FALSE( - *repo_fetch_branch->CheckCommitExists(kRootCommit, logger)); - - // fetch branch - REQUIRE(repo_fetch_branch->LocalFetchViaTmpRepo( - *repo_path, "master", logger)); - - // check commit is there after fetch - CHECK(*repo_fetch_branch->CheckCommitExists(kRootCommit, logger)); - } - } } TEST_CASE("Multi-threaded fake repository operations", "[git_repo]") { @@ -516,7 +575,7 @@ TEST_CASE("Multi-threaded fake repository operations", "[git_repo]") { threads.reserve(kNumThreads); SECTION("Lookups in the same ODB") { - constexpr int NUM_CASES = 9; + constexpr int NUM_CASES = 10; for (int id{}; id < kNumThreads; ++id) { threads.emplace_back( [&remote_cas, &remote_repo_path, &logger, &starting_signal]( @@ -603,6 +662,20 @@ TEST_CASE("Multi-threaded fake repository operations", "[git_repo]") { auto remote_repo = GitRepo::Open(remote_cas); REQUIRE(remote_repo); REQUIRE(remote_repo->IsRepoFake()); + // Write blobs + auto res = remote_repo->WriteBlob( + std::to_string(tid), logger); + REQUIRE(res); + // ...including existing content + res = remote_repo->WriteBlob("foo", logger); + REQUIRE(res); + CHECK(res == kFooId); + } break; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + case 7: { + auto remote_repo = GitRepo::Open(remote_cas); + REQUIRE(remote_repo); + REQUIRE(remote_repo->IsRepoFake()); // Get subtree entry id from path auto path_baz = *remote_repo_path / "baz"; auto entry_baz_p = remote_repo->GetSubtreeFromPath( @@ -611,7 +684,7 @@ TEST_CASE("Multi-threaded fake repository operations", "[git_repo]") { CHECK(*entry_baz_p == kBazId); } break; // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - case 7: { + case 8: { auto remote_repo = GitRepo::Open(remote_cas); REQUIRE(remote_repo); REQUIRE(remote_repo->IsRepoFake()); @@ -621,7 +694,7 @@ TEST_CASE("Multi-threaded fake repository operations", "[git_repo]") { CHECK(*result_containing); } break; // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - case 8: { + case 9: { auto remote_repo = GitRepo::Open(remote_cas); REQUIRE(remote_repo); REQUIRE(remote_repo->IsRepoFake()); |