summaryrefslogtreecommitdiff
path: root/test/buildtool/execution_engine/traverser/traverser.test.cpp
diff options
context:
space:
mode:
authorKlaus Aehlig <klaus.aehlig@huawei.com>2022-02-22 17:03:21 +0100
committerKlaus Aehlig <klaus.aehlig@huawei.com>2022-02-22 17:03:21 +0100
commit619def44c1cca9f3cdf63544d5f24f2c7a7d9b77 (patch)
tree01868de723cb82c86842f33743fa7b14e24c1fa3 /test/buildtool/execution_engine/traverser/traverser.test.cpp
downloadjustbuild-619def44c1cca9f3cdf63544d5f24f2c7a7d9b77.tar.gz
Initial self-hosting commit
This is the initial version of our tool that is able to build itself. In can be bootstrapped by ./bin/bootstrap.py Co-authored-by: Oliver Reiche <oliver.reiche@huawei.com> Co-authored-by: Victor Moreno <victor.moreno1@huawei.com>
Diffstat (limited to 'test/buildtool/execution_engine/traverser/traverser.test.cpp')
-rw-r--r--test/buildtool/execution_engine/traverser/traverser.test.cpp836
1 files changed, 836 insertions, 0 deletions
diff --git a/test/buildtool/execution_engine/traverser/traverser.test.cpp b/test/buildtool/execution_engine/traverser/traverser.test.cpp
new file mode 100644
index 00000000..63d1fad6
--- /dev/null
+++ b/test/buildtool/execution_engine/traverser/traverser.test.cpp
@@ -0,0 +1,836 @@
+#include <algorithm>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <variant>
+#include <vector>
+
+#include "catch2/catch.hpp"
+#include "src/buildtool/common/artifact_factory.hpp"
+#include "src/buildtool/execution_engine/dag/dag.hpp"
+#include "src/buildtool/execution_engine/traverser/traverser.hpp"
+#include "test/utils/container_matchers.hpp"
+
+namespace {
+
+auto const kNumJobs = std::max(1U, std::thread::hardware_concurrency());
+
+class TestBuildInfo {
+ public:
+ [[nodiscard]] auto CorrectlyBuilt() const noexcept
+ -> std::unordered_set<ArtifactIdentifier> {
+ return correctly_built_;
+ }
+
+ [[nodiscard]] auto IncorrectlyBuilt() const noexcept
+ -> std::unordered_set<ArtifactIdentifier> {
+ return incorrectly_built_;
+ }
+
+ [[nodiscard]] auto ArtifactsUploaded() const noexcept
+ -> std::unordered_set<ArtifactIdentifier> {
+ return artifacts_uploaded_;
+ }
+
+ [[nodiscard]] auto WasUploadRepeated() noexcept -> bool {
+ return not uploaded_more_than_once_.empty();
+ }
+
+ [[nodiscard]] auto Name() const noexcept -> std::string { return name_; }
+
+ void SetName(std::string const& name) noexcept {
+ std::lock_guard lock{mutex_};
+ name_ = name;
+ }
+
+ void SetName(std::string&& name) noexcept {
+ std::lock_guard lock{mutex_};
+ name_ = std::move(name);
+ }
+
+ [[nodiscard]] auto InsertCorrectlyBuilt(
+ ArtifactIdentifier const& artifact_id) -> bool {
+ std::lock_guard lock{mutex_};
+ auto const [_, first_time_added] = correctly_built_.insert(artifact_id);
+ return first_time_added;
+ }
+
+ [[nodiscard]] auto InsertIncorrectlyBuilt(
+ ArtifactIdentifier const& artifact_id) -> bool {
+ std::lock_guard lock{mutex_};
+ auto const [_, first_time_added] =
+ incorrectly_built_.insert(artifact_id);
+ return first_time_added;
+ }
+
+ auto InsertArtifactUploaded(ArtifactIdentifier const& artifact_id) -> bool {
+ std::lock_guard lock{mutex_};
+ auto const [_, first_time_added] =
+ artifacts_uploaded_.insert(artifact_id);
+ if (not first_time_added) {
+ uploaded_more_than_once_.insert(artifact_id);
+ }
+ return true;
+ }
+
+ private:
+ std::unordered_set<ArtifactIdentifier> correctly_built_{};
+ std::unordered_set<ArtifactIdentifier> incorrectly_built_{};
+ std::unordered_set<ArtifactIdentifier> artifacts_uploaded_{};
+ std::unordered_set<ArtifactIdentifier> uploaded_more_than_once_{};
+ std::string name_{};
+ std::mutex mutex_;
+};
+
+class TestExecutor {
+ public:
+ explicit TestExecutor(TestBuildInfo* info) noexcept
+ : name_{info->Name()}, build_info_{info} {}
+
+ [[nodiscard]] auto Process(
+ gsl::not_null<DependencyGraph::ActionNode const*> const& action)
+ const noexcept -> bool {
+ try {
+ build_info_->SetName(name_);
+ bool const all_deps_available = AllAvailable(action->Children());
+ if (all_deps_available) {
+ for (auto const& [name, node] : action->OutputFiles()) {
+ if (not build_info_->InsertCorrectlyBuilt(
+ node->Content().Id())) {
+ [[maybe_unused]] auto was_it_added =
+ build_info_->InsertIncorrectlyBuilt(
+ node->Content().Id());
+ return false;
+ }
+ }
+ return true;
+ }
+ for (auto const& [name, node] : action->OutputFiles()) {
+ [[maybe_unused]] auto was_it_added =
+ build_info_->InsertIncorrectlyBuilt(node->Content().Id());
+ }
+ } catch (...) {
+ }
+ return false;
+ }
+
+ [[nodiscard]] auto Process(
+ gsl::not_null<DependencyGraph::ArtifactNode const*> const& artifact)
+ const noexcept -> bool {
+ try {
+ build_info_->InsertArtifactUploaded(artifact->Content().Id());
+ } catch (...) {
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ std::string const name_;
+ TestBuildInfo* build_info_;
+
+ template <typename Container>
+ [[nodiscard]] auto AllAvailable(Container&& c) const noexcept -> bool {
+ return std::all_of(std::begin(c), std::end(c), [](auto node) {
+ return node->TraversalState()->IsAvailable();
+ });
+ }
+};
+
+// Class to simplify the writing of tests, checking that no outputs are repeated
+// and keeping track of what needs to be built
+class TestProject {
+ public:
+ auto AddOutputInputPair(std::string const& action_id,
+ std::vector<std::string> const& outputs,
+ std::vector<nlohmann::json> const& inputs) -> bool {
+ std::vector<std::string> command;
+ command.emplace_back("BUILD");
+ for (auto const& output : outputs) {
+ command.push_back(output);
+ auto const out_id = ArtifactDescription{
+ action_id,
+ std::filesystem::path{
+ output}}.Id();
+ auto [_, is_inserted] = artifacts_to_be_built_.insert(out_id);
+ if (!is_inserted) {
+ return false;
+ }
+ }
+ auto inputs_desc = ActionDescription::inputs_t{};
+ if (!inputs.empty()) {
+ command.emplace_back("FROM");
+ for (auto const& input_desc : inputs) {
+ auto artifact = ArtifactDescription::FromJson(input_desc);
+ REQUIRE(artifact);
+ auto const input_id = artifact->Id();
+ command.push_back(input_id);
+ inputs_desc.emplace(input_id, *artifact);
+ if (ArtifactFactory::IsLocal(input_desc)) {
+ local_artifacts_.insert(input_id);
+ }
+ }
+ }
+ graph_full_description_.emplace_back(ActionDescription{
+ outputs, {}, Action{action_id, command, {}}, inputs_desc});
+ return true;
+ }
+
+ auto FillGraph(gsl::not_null<DependencyGraph*> const& g) -> bool {
+ return g->Add(graph_full_description_);
+ }
+
+ [[nodiscard]] auto ArtifactsToBeBuilt() const noexcept
+ -> std::unordered_set<ArtifactIdentifier> {
+ return artifacts_to_be_built_;
+ }
+
+ [[nodiscard]] auto LocalArtifacts() const noexcept
+ -> std::unordered_set<ArtifactIdentifier> {
+ return local_artifacts_;
+ }
+
+ private:
+ std::vector<ActionDescription> graph_full_description_{};
+ std::unordered_set<ArtifactIdentifier> artifacts_to_be_built_{};
+ std::unordered_set<ArtifactIdentifier> local_artifacts_{};
+};
+
+} // namespace
+
+TEST_CASE("Executable", "[traverser]") {
+ TestProject p;
+ CHECK(p.AddOutputInputPair(
+ "action",
+ {"executable"},
+ {ArtifactFactory::DescribeLocalArtifact("main.cpp", "")}));
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted";
+ build_info.SetName(name);
+ SECTION("Traverse()") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(executable)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+
+ auto const exec_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("action",
+ "executable"));
+ auto const traversed = traverser.Traverse({exec_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+}
+
+TEST_CASE("Executable depends on library", "[traverser]") {
+ TestProject p;
+ CHECK(p.AddOutputInputPair(
+ "make_exe",
+ {"executable"},
+ {ArtifactFactory::DescribeLocalArtifact("main.cpp", "repo"),
+ ArtifactFactory::DescribeActionArtifact("make_lib", "library")}));
+ CHECK(p.AddOutputInputPair(
+ "make_lib",
+ {"library"},
+ {ArtifactFactory::DescribeLocalArtifact("library.hpp", "repo"),
+ ArtifactFactory::DescribeLocalArtifact("library.cpp", "repo")}));
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted";
+ build_info.SetName(name);
+ SECTION("Full build (without specifying artifacts)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Full build (executable)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const exec_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_exe",
+ "executable"));
+ CHECK(traverser.Traverse({exec_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Only build library") {
+ auto const lib_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_lib", "library"));
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({lib_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {lib_id}));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ auto const lib_cpp_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("library.cpp", "repo"));
+ auto const lib_hpp_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("library.hpp", "repo"));
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {lib_cpp_id, lib_hpp_id}));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+}
+
+TEST_CASE("Two artifacts depend on another", "[traverser]") {
+ TestProject p;
+ auto const dep_desc =
+ ArtifactFactory::DescribeActionArtifact("make_dep", "dep");
+ auto const dep_id = ArtifactFactory::Identifier(dep_desc);
+ CHECK(p.AddOutputInputPair("action1", {"toplevel1"}, {dep_desc}));
+ CHECK(p.AddOutputInputPair("action2", {"toplevel2"}, {dep_desc}));
+ CHECK(p.AddOutputInputPair(
+ "make_dep",
+ {"dep"},
+ {ArtifactFactory::DescribeLocalArtifact("leaf1", "repo"),
+ ArtifactFactory::DescribeLocalArtifact("leaf2", "repo")}));
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted";
+ build_info.SetName(name);
+ SECTION("Full build") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Only specified top-level artifact is built") {
+ auto const toplevel1_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("action1", "toplevel1"));
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({toplevel1_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {toplevel1_id, dep_id}));
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK(build_info.Name() == name);
+ }
+}
+
+TEST_CASE("Action with two outputs, no deps", "[traverser]") {
+ TestProject p;
+ CHECK(p.AddOutputInputPair("make_outputs", {"output1", "output2"}, {}));
+ auto const output1_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_outputs", "output1"));
+ auto const output2_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_outputs", "output2"));
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted";
+ build_info.SetName(name);
+ SECTION("Traverse()") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(output1)") {
+
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({output1_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(output1, output2)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({output1_id, output2_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+}
+
+TEST_CASE("Action with two outputs, one dep", "[traverser]") {
+ TestProject p;
+ CHECK(p.AddOutputInputPair(
+ "make_outputs",
+ {"output1", "output2"},
+ {ArtifactFactory::DescribeLocalArtifact("dep", "repo")}));
+ auto const output1_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_outputs", "output1"));
+ auto const output2_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_outputs", "output2"));
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted";
+ build_info.SetName(name);
+ SECTION("Traverse()") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(output1)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({output1_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(output1, output2)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({output1_id, output2_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(dep, output2)") {
+ auto const dep_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("dep", "repo"));
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({dep_id, output2_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+}
+
+TEST_CASE("Action with two outputs, actions depend on each of outputs",
+ "[traverser]") {
+ TestProject p;
+ CHECK(p.AddOutputInputPair("make_outputs", {"output1", "output2"}, {}));
+ auto const output1_desc =
+ ArtifactFactory::DescribeActionArtifact("make_outputs", "output1");
+ auto const output1_id = ArtifactFactory::Identifier(output1_desc);
+ auto const output2_desc =
+ ArtifactFactory::DescribeActionArtifact("make_outputs", "output2");
+ auto const output2_id = ArtifactFactory::Identifier(output2_desc);
+
+ CHECK(p.AddOutputInputPair("consumer1", {"exec1"}, {output1_desc}));
+ auto const exec1_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("consumer1", "exec1"));
+
+ CHECK(p.AddOutputInputPair("consumer2", {"exec2"}, {output2_desc}));
+ auto const exec2_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("consumer2", "exec2"));
+
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted";
+ build_info.SetName(name);
+ SECTION("Traverse()") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(exec1)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({exec1_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {exec1_id, output1_id, output2_id}));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(exec2, output1)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({output1_id, exec2_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {exec2_id, output1_id, output2_id}));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Traverse(exec1, exec2)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ auto const traversed = traverser.Traverse({exec1_id, exec2_id});
+ CHECK(traversed);
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+}
+
+TEST_CASE("lib2 depends on lib1, executable depends on lib1 and lib2") {
+ TestProject p;
+ auto const lib1_desc =
+ ArtifactFactory::DescribeActionArtifact("make_lib1", "lib1");
+ auto const lib1_id = ArtifactFactory::Identifier(lib1_desc);
+
+ auto const lib2_desc =
+ ArtifactFactory::DescribeActionArtifact("make_lib2", "lib2");
+ auto const lib2_id = ArtifactFactory::Identifier(lib2_desc);
+
+ auto const exec_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeActionArtifact("make_exe", "executable"));
+
+ CHECK(p.AddOutputInputPair(
+ "make_exe",
+ {"executable"},
+ {ArtifactFactory::DescribeLocalArtifact("main.cpp", "repo"),
+ lib1_desc,
+ lib2_desc}));
+
+ CHECK(p.AddOutputInputPair(
+ "make_lib1",
+ {"lib1"},
+ {ArtifactFactory::DescribeLocalArtifact("lib1.hpp", "repo"),
+ ArtifactFactory::DescribeLocalArtifact("lib1.cpp", "repo")}));
+ CHECK(p.AddOutputInputPair(
+ "make_lib2",
+ {"lib2"},
+ {lib1_desc,
+ ArtifactFactory::DescribeLocalArtifact("lib2.hpp", "repo"),
+ ArtifactFactory::DescribeLocalArtifact("lib2.cpp", "repo")}));
+
+ DependencyGraph g;
+ CHECK(p.FillGraph(&g));
+ TestBuildInfo build_info;
+ std::string name = "This is a long name that shouldn't be corrupted ";
+ build_info.SetName(name);
+ SECTION(" Full build(without specifying artifacts) ") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse());
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Full build (executable)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({exec_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Full build (executable + lib1)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({exec_id, lib1_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Full build (executable + lib2)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({exec_id, lib2_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("Full build (executable + lib1 + lib2)") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({exec_id, lib1_id, lib2_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+ SECTION("First call does not build all artifacts") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({lib1_id}));
+ CHECK(traverser.Traverse({exec_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.ArtifactsToBeBuilt()));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ p.LocalArtifacts()));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+
+ SECTION(
+ "Traverse(lib2), executable is not built even if lib1 would notify its "
+ "action") {
+ {
+ TestExecutor runner{&build_info};
+ Traverser traverser(runner, g, kNumJobs);
+ CHECK(traverser.Traverse({lib2_id}));
+ }
+ CHECK_THAT(
+ build_info.CorrectlyBuilt(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {lib1_id, lib2_id}));
+ CHECK(build_info.IncorrectlyBuilt().empty());
+ auto const lib1_hpp_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("lib1.hpp", "repo"));
+ auto const lib1_cpp_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("lib1.cpp", "repo"));
+ auto const lib2_hpp_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("lib2.hpp", "repo"));
+ auto const lib2_cpp_id = ArtifactFactory::Identifier(
+ ArtifactFactory::DescribeLocalArtifact("lib2.cpp", "repo"));
+ CHECK_THAT(
+ build_info.ArtifactsUploaded(),
+ HasSameUniqueElementsAs<std::unordered_set<ArtifactIdentifier>>(
+ {lib1_hpp_id, lib1_cpp_id, lib2_hpp_id, lib2_cpp_id}));
+ CHECK_FALSE(build_info.WasUploadRepeated());
+ CHECK(build_info.Name() == name);
+ }
+}