diff --git a/tests/ConcurrentExternalFetch/main.cpp b/tests/ConcurrentExternalFetch/main.cpp index 2d65ea7..6ed3f18 100644 --- a/tests/ConcurrentExternalFetch/main.cpp +++ b/tests/ConcurrentExternalFetch/main.cpp @@ -7,17 +7,19 @@ namespace fs = std::filesystem; using namespace Crafter; // Builds that run at the same time share their dependencies' clones in the -// cache: the tests `crafter-build test --jobs=N` builds side by side each -// declare the same ExternalDependency, and two crafter-builds can run at -// once. Fetching a clone that already exists ran `git pull` (and the CMake -// build) in it with nothing to keep a second fetch out, and the second git -// fails on the first one's index.lock. A clone's fetch and build now hold a -// lock of their own. +// cache, and two crafter-builds can run at once. Fetching a clone that +// already exists ran `git pull` (and the CMake build) in it with nothing to +// keep a second fetch out, and the second git fails on the first one's +// index.lock. A clone's fetch and build now hold a lock of their own. Within +// one run, the configurations that declare the same dependency share one +// fetch of it instead (SharedExternalFetch). // // Eight threads fetch the same tracked branch of a local repository at // once, a few rounds over, the branch moving between rounds so every pull -// has work to do; every fetch has to succeed. Everything is local; -// XDG_CACHE_HOME keeps the clone out of the real cache. +// has work to do; every fetch has to succeed. Each thread fetches through +// GitFetch and through BuildExternal, which every build of an +// ExternalDependency calls, each into a clone of its own. Everything is +// local; XDG_CACHE_HOME keeps the clones out of the real cache. extern "C" int setenv(const char* name, const char* value, int overwrite); @@ -42,6 +44,13 @@ namespace { Git(repo, "add round.txt"); Git(repo, std::format("commit -q -m round-{}", round)); } + + std::uint32_t Round(const fs::path& clone) { + std::ifstream in(clone / "round.txt"); + std::uint32_t round = 0; + in >> round; + return round; + } } int main() { @@ -60,6 +69,12 @@ int main() { Commit(repo, 0); const GitSource source { .url = std::format("file://{}", repo.string()), .branch = "main" }; const fs::path clone = GitFetch(source); + // A clone of its own: BuildExternal keys the cache differently. + ExternalDependency dep; + dep.name = "shared"; + dep.source = source; + const std::string cloned = BuildExternal(dep, HostTarget()); + Check(cloned.empty(), std::format("BuildExternal clones the repository: {}", cloned)); constexpr std::uint32_t threads = 8; constexpr std::uint32_t rounds = 4; @@ -70,20 +85,23 @@ int main() { std::vector fetchers; for (std::uint32_t i = 0; i < threads; ++i) { fetchers.emplace_back([&] { + std::string error; try { (void)GitFetch(source); + error = BuildExternal(dep, HostTarget()); } catch (const std::exception& e) { - std::lock_guard lock(errorsMutex); - errors.push_back(e.what()); + error = e.what(); } + if (error.empty()) return; + std::lock_guard lock(errorsMutex); + errors.push_back(std::move(error)); }); } } - Check(errors.empty(), std::format("{} fetches at once, {} rounds: every one succeeds ({} failed{}{})", threads, rounds, errors.size(), errors.empty() ? "" : ", the first: ", errors.empty() ? "" : errors.front())); - std::ifstream round(clone / "round.txt"); - std::uint32_t last = 0; - round >> last; - Check(last == rounds, std::format("and the clone ends at the last round ({})", last)); + Check(errors.empty(), std::format("{} threads fetching at once, {} rounds: every fetch succeeds ({} failed{}{})", threads, rounds, errors.size(), errors.empty() ? "" : ", the first: ", errors.empty() ? "" : errors.front())); + Check(Round(clone) == rounds, std::format("and GitFetch's clone ends at the last round ({})", Round(clone))); + const fs::path externalClone = ExternalCloneDir(dep, HostTarget()); + Check(Round(externalClone) == rounds, std::format("and so does BuildExternal's ({})", Round(externalClone))); std::error_code ec; fs::remove_all(scratch, ec);