diff options
Diffstat (limited to 'test/buildtool/execution_engine/executor')
10 files changed, 1180 insertions, 0 deletions
diff --git a/test/buildtool/execution_engine/executor/TARGETS b/test/buildtool/execution_engine/executor/TARGETS new file mode 100644 index 00000000..9bf1c50e --- /dev/null +++ b/test/buildtool/execution_engine/executor/TARGETS @@ -0,0 +1,71 @@ +{ "executor_api_tests": + { "type": ["@", "rules", "CC", "library"] + , "name": ["executor_api_tests"] + , "hdrs": ["executor_api.test.hpp"] + , "stage": ["test", "buildtool", "execution_engine", "executor"] + } +, "executor": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["executor"] + , "srcs": ["executor.test.cpp"] + , "deps": + [ ["src/buildtool/common", "artifact_factory"] + , ["src/buildtool/execution_api/common", "common"] + , ["src/buildtool/execution_engine/dag", "dag"] + , ["src/buildtool/execution_engine/executor", "executor"] + , ["test", "catch-main"] + , ["@", "catch2", "", "catch2"] + ] + , "stage": ["test", "buildtool", "execution_engine", "executor"] + } +, "local": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["local"] + , "srcs": ["executor_api_local.test.cpp"] + , "data": ["test_data"] + , "deps": + [ "executor_api_tests" + , ["src/buildtool/common", "artifact_factory"] + , ["src/buildtool/execution_api/local", "local"] + , ["src/buildtool/execution_api/remote", "config"] + , ["src/buildtool/execution_engine/dag", "dag"] + , ["src/buildtool/execution_engine/executor", "executor"] + , ["test/utils", "catch-main-remote-execution"] + , ["test/utils", "local_hermeticity"] + , ["@", "catch2", "", "catch2"] + ] + , "stage": ["test", "buildtool", "execution_engine", "executor"] + } +, "remote_bazel": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["remote_bazel"] + , "srcs": ["executor_api_remote_bazel.test.cpp"] + , "data": ["test_data"] + , "deps": + [ "executor_api_tests" + , ["src/buildtool/common", "artifact_factory"] + , ["src/buildtool/execution_api/remote", "bazel"] + , ["src/buildtool/execution_api/remote", "config"] + , ["src/buildtool/execution_engine/executor", "executor"] + , ["test/utils", "catch-main-remote-execution"] + , ["@", "catch2", "", "catch2"] + ] + , "stage": ["test", "buildtool", "execution_engine", "executor"] + } +, "test_data": + { "type": ["@", "rules", "data", "staged"] + , "srcs": + [ "data/greeter/greet.cpp" + , "data/greeter/greet.hpp" + , "data/greeter/greet_mod.cpp" + , "data/greeter/main.cpp" + , "data/hello_world/main.cpp" + ] + , "stage": ["test", "buildtool", "execution_engine", "executor"] + } +, "TESTS": + { "type": "install" + , "tainted": ["test"] + , "deps": ["executor", "local", "remote_bazel"] + } +}
\ No newline at end of file diff --git a/test/buildtool/execution_engine/executor/data/greeter/greet.cpp b/test/buildtool/execution_engine/executor/data/greeter/greet.cpp new file mode 100644 index 00000000..f1a1cf6b --- /dev/null +++ b/test/buildtool/execution_engine/executor/data/greeter/greet.cpp @@ -0,0 +1,6 @@ +#include <iostream> +#include "greet.hpp" + +void greet(std::string const& name) { + std::cout << "Hello " << name << std::endl; +} diff --git a/test/buildtool/execution_engine/executor/data/greeter/greet.hpp b/test/buildtool/execution_engine/executor/data/greeter/greet.hpp new file mode 100644 index 00000000..d4cb767d --- /dev/null +++ b/test/buildtool/execution_engine/executor/data/greeter/greet.hpp @@ -0,0 +1,3 @@ +#include <string> + +void greet(std::string const& name); diff --git a/test/buildtool/execution_engine/executor/data/greeter/greet_mod.cpp b/test/buildtool/execution_engine/executor/data/greeter/greet_mod.cpp new file mode 100644 index 00000000..550a7bf8 --- /dev/null +++ b/test/buildtool/execution_engine/executor/data/greeter/greet_mod.cpp @@ -0,0 +1,8 @@ +#include <iostream> +#include "greet.hpp" + +// this is a modification that has no effect on the produced binary + +void greet(std::string const& name) { + std::cout << "Hello " << name << std::endl; +} diff --git a/test/buildtool/execution_engine/executor/data/greeter/main.cpp b/test/buildtool/execution_engine/executor/data/greeter/main.cpp new file mode 100644 index 00000000..4d51ee4a --- /dev/null +++ b/test/buildtool/execution_engine/executor/data/greeter/main.cpp @@ -0,0 +1,6 @@ +#include "greet.hpp" + +int main(void) { + greet("devcloud"); + return 0; +} diff --git a/test/buildtool/execution_engine/executor/data/hello_world/main.cpp b/test/buildtool/execution_engine/executor/data/hello_world/main.cpp new file mode 100644 index 00000000..f7eb16a1 --- /dev/null +++ b/test/buildtool/execution_engine/executor/data/hello_world/main.cpp @@ -0,0 +1,6 @@ +#include <iostream> + +int main(void) { + std::cout << "Hello World!" << std::endl; + return 0; +} diff --git a/test/buildtool/execution_engine/executor/executor.test.cpp b/test/buildtool/execution_engine/executor/executor.test.cpp new file mode 100755 index 00000000..63f41521 --- /dev/null +++ b/test/buildtool/execution_engine/executor/executor.test.cpp @@ -0,0 +1,358 @@ +#include <string> +#include <unordered_map> +#include <vector> + +#include "catch2/catch.hpp" +#include "src/buildtool/common/artifact_factory.hpp" +#include "src/buildtool/execution_api/common/execution_api.hpp" +#include "src/buildtool/execution_engine/executor/executor.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" + +/// \brief Mockup API test config. +struct TestApiConfig { + struct TestArtifactConfig { + bool uploads{}; + bool available{}; + }; + + struct TestExecutionConfig { + bool failed{}; + std::vector<std::string> outputs{}; + }; + + struct TestResponseConfig { + bool cached{}; + int exit_code{}; + }; + + std::unordered_map<std::string, TestArtifactConfig> artifacts{}; + TestExecutionConfig execution; + TestResponseConfig response; +}; + +// forward declarations +class TestApi; +class TestAction; +class TestResponse; + +/// \brief Mockup Response, stores only config and action result +class TestResponse : public IExecutionResponse { + friend class TestAction; + + public: + [[nodiscard]] auto Status() const noexcept -> StatusCode final { + return StatusCode::Success; + } + [[nodiscard]] auto ExitCode() const noexcept -> int final { + return config_.response.exit_code; + } + [[nodiscard]] auto IsCached() const noexcept -> bool final { + return config_.response.cached; + } + [[nodiscard]] auto HasStdErr() const noexcept -> bool final { return true; } + [[nodiscard]] auto HasStdOut() const noexcept -> bool final { return true; } + [[nodiscard]] auto StdErr() noexcept -> std::string final { return {}; } + [[nodiscard]] auto StdOut() noexcept -> std::string final { return {}; } + [[nodiscard]] auto ActionDigest() const noexcept -> std::string final { + return {}; + } + [[nodiscard]] auto Artifacts() const noexcept -> ArtifactInfos final { + ArtifactInfos artifacts{}; + artifacts.reserve(config_.execution.outputs.size()); + + // collect files and store them + for (auto const& path : config_.execution.outputs) { + try { + artifacts.emplace(path, + Artifact::ObjectInfo{ArtifactDigest{path, 0}, + ObjectType::File}); + } catch (...) { + return {}; + } + } + + return artifacts; + } + + private: + TestApiConfig config_{}; + explicit TestResponse(TestApiConfig config) noexcept + : config_{std::move(config)} {} +}; + +/// \brief Mockup Action, stores only config +class TestAction : public IExecutionAction { + friend class TestApi; + + public: + auto Execute(Logger const* /*unused*/) noexcept + -> IExecutionResponse::Ptr final { + if (config_.execution.failed) { + return nullptr; + } + return IExecutionResponse::Ptr{new TestResponse{config_}}; + } + void SetCacheFlag(CacheFlag /*unused*/) noexcept final {} + void SetTimeout(std::chrono::milliseconds /*unused*/) noexcept final {} + + private: + TestApiConfig config_{}; + explicit TestAction(TestApiConfig config) noexcept + : config_{std::move(config)} {} +}; + +/// \brief Mockup Api, use config to create action and handle artifact upload +class TestApi : public IExecutionApi { + public: + explicit TestApi(TestApiConfig config) noexcept + : config_{std::move(config)} {} + + auto CreateAction( + ArtifactDigest const& /*unused*/, + std::vector<std::string> const& /*unused*/, + std::vector<std::string> const& /*unused*/, + std::vector<std::string> const& /*unused*/, + std::map<std::string, std::string> const& /*unused*/, + std::map<std::string, std::string> const& /*unused*/) noexcept + -> IExecutionAction::Ptr final { + return IExecutionAction::Ptr{new TestAction(config_)}; + } + auto RetrieveToPaths( + std::vector<Artifact::ObjectInfo> const& /*unused*/, + std::vector<std::filesystem::path> const& /*unused*/) noexcept + -> bool final { + return false; // not needed by Executor + } + auto RetrieveToFds(std::vector<Artifact::ObjectInfo> const& /*unused*/, + std::vector<int> const& /*unused*/) noexcept + -> bool final { + return false; // not needed by Executor + } + auto Upload(BlobContainer const& blobs, bool /*unused*/) noexcept + -> bool final { + for (auto const& blob : blobs) { + if (config_.artifacts[blob.data].uploads) { + continue; // for local artifacts + } + if (config_.artifacts[blob.digest.hash()].uploads) { + continue; // for known and action artifacts + } + return false; + } + return true; + } + auto UploadTree( + std::vector< + DependencyGraph::NamedArtifactNodePtr> const& /*unused*/) noexcept + -> std::optional<ArtifactDigest> final { + return ArtifactDigest{}; // not needed by Executor + } + [[nodiscard]] auto IsAvailable(ArtifactDigest const& digest) const noexcept + -> bool final { + try { + return config_.artifacts.at(digest.hash()).available; + } catch (std::exception const& /* unused */) { + return false; + } + } + + private: + TestApiConfig config_{}; +}; + +static void SetupConfig(std::filesystem::path const& ws) { + auto info = RepositoryConfig::RepositoryInfo{FileRoot{ws}}; + RepositoryConfig::Instance().Reset(); + RepositoryConfig::Instance().SetInfo("", std::move(info)); +} + +[[nodiscard]] static auto CreateTest(gsl::not_null<DependencyGraph*> const& g, + std::filesystem::path const& ws) + -> TestApiConfig { + using path = std::filesystem::path; + SetupConfig(ws); + + auto const local_cpp_desc = ArtifactDescription{path{"local.cpp"}, ""}; + auto const local_cpp_id = local_cpp_desc.Id(); + + auto const known_cpp_desc = + ArtifactDescription{ArtifactDigest{"known.cpp", 0}, ObjectType::File}; + auto const known_cpp_id = known_cpp_desc.Id(); + + auto const test_action_desc = ActionDescription{ + {"output1.exe", "output2.exe"}, + {}, + Action{"test_action", {"cmd", "line"}, {}}, + {{"local.cpp", local_cpp_desc}, {"known.cpp", known_cpp_desc}}}; + + CHECK(g->AddAction(test_action_desc)); + CHECK(FileSystemManager::WriteFile("local.cpp", ws / "local.cpp")); + + TestApiConfig config{}; + + config.artifacts["local.cpp"].uploads = true; + config.artifacts["known.cpp"].available = true; + config.artifacts["output1.exe"].available = true; + config.artifacts["output2.exe"].available = true; + + config.execution.failed = false; + config.execution.outputs = {"output1.exe", "output2.exe"}; + + config.response.cached = true; + config.response.exit_code = 0; + + return config; +} + +TEST_CASE("Executor: Process artifact", "[executor]") { + std::filesystem::path workspace_path{ + "test/buildtool/execution_engine/executor"}; + DependencyGraph g; + auto config = CreateTest(&g, workspace_path); + + auto const local_cpp_desc = + ArtifactFactory::DescribeLocalArtifact("local.cpp", ""); + auto const local_cpp_id = ArtifactFactory::Identifier(local_cpp_desc); + + auto const known_cpp_desc = ArtifactFactory::DescribeKnownArtifact( + "known.cpp", 0, ObjectType::File); + auto const known_cpp_id = ArtifactFactory::Identifier(known_cpp_desc); + + SECTION("Processing succeeds for valid config") { + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + } + + SECTION("Processing fails if uploading local artifact failed") { + config.artifacts["local.cpp"].uploads = false; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(not runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + } + + SECTION("Processing fails if known artifact is not available") { + config.artifacts["known.cpp"].available = false; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(not runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + } +} + +TEST_CASE("Executor: Process action", "[executor]") { + std::filesystem::path workspace_path{ + "test/buildtool/execution_engine/executor"}; + + DependencyGraph g; + auto config = CreateTest(&g, workspace_path); + + auto const local_cpp_desc = + ArtifactFactory::DescribeLocalArtifact("local.cpp", ""); + auto const local_cpp_id = ArtifactFactory::Identifier(local_cpp_desc); + + auto const known_cpp_desc = ArtifactFactory::DescribeKnownArtifact( + "known.cpp", 0, ObjectType::File); + auto const known_cpp_id = ArtifactFactory::Identifier(known_cpp_desc); + + ActionIdentifier action_id{"test_action"}; + auto const output1_desc = + ArtifactFactory::DescribeActionArtifact(action_id, "output1.exe"); + auto const output1_id = ArtifactFactory::Identifier(output1_desc); + + auto const output2_desc = + ArtifactFactory::DescribeActionArtifact(action_id, "output2.exe"); + auto const output2_id = ArtifactFactory::Identifier(output2_desc); + + SECTION("Processing succeeds for valid config") { + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + CHECK(runner.Process(g.ActionNodeWithId(action_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(output1_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(output2_id))); + } + + SECTION("Processing succeeds even if result was is not cached") { + config.response.cached = false; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + CHECK(runner.Process(g.ActionNodeWithId(action_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(output1_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(output2_id))); + } + + SECTION("Processing succeeds even if output is not available in CAS") { + config.artifacts["output2.exe"].available = false; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + CHECK(runner.Process(g.ActionNodeWithId(action_id))); + + // Note: Both output digests should be created via SaveDigests(), + // but processing output2.exe fails as it is not available in CAS. + CHECK(runner.Process(g.ArtifactNodeWithId(output1_id))); + CHECK(not runner.Process(g.ArtifactNodeWithId(output2_id))); + } + + SECTION("Processing fails if execution failed") { + config.execution.failed = true; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + CHECK(not runner.Process(g.ActionNodeWithId(action_id))); + CHECK(not runner.Process(g.ArtifactNodeWithId(output1_id))); + CHECK(not runner.Process(g.ArtifactNodeWithId(output2_id))); + } + + SECTION("Processing fails if exit code is non-zero") { + config.response.exit_code = 1; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + CHECK(not runner.Process(g.ActionNodeWithId(action_id))); + + // Note: Both output digests should be missing as SaveDigests() for + // both is only called if processing action succeeds. + CHECK(not runner.Process(g.ArtifactNodeWithId(output1_id))); + CHECK(not runner.Process(g.ArtifactNodeWithId(output2_id))); + } + + SECTION("Processing fails if any output is missing") { + config.execution.outputs = {"output1.exe" /*, "output2.exe"*/}; + + auto api = TestApi::Ptr{new TestApi{config}}; + Executor runner{api.get(), {}}; + + CHECK(runner.Process(g.ArtifactNodeWithId(local_cpp_id))); + CHECK(runner.Process(g.ArtifactNodeWithId(known_cpp_id))); + CHECK(not runner.Process(g.ActionNodeWithId(action_id))); + + // Note: Both output digests should be missing as SaveDigests() for + // both is only called if processing action succeeds. + CHECK(not runner.Process(g.ArtifactNodeWithId(output1_id))); + CHECK(not runner.Process(g.ArtifactNodeWithId(output2_id))); + } +} diff --git a/test/buildtool/execution_engine/executor/executor_api.test.hpp b/test/buildtool/execution_engine/executor/executor_api.test.hpp new file mode 100755 index 00000000..48d37d98 --- /dev/null +++ b/test/buildtool/execution_engine/executor/executor_api.test.hpp @@ -0,0 +1,615 @@ +#ifndef INCLUDED_SRC_TEST_BUILDTOOL_EXECUTION_ENGINE_EXECUTOR_EXECUTOR_API_TEST_HPP +#define INCLUDED_SRC_TEST_BUILDTOOL_EXECUTION_ENGINE_EXECUTOR_EXECUTOR_API_TEST_HPP + +#include <functional> + +#include "catch2/catch.hpp" +#include "src/buildtool/common/artifact.hpp" +#include "src/buildtool/common/artifact_description.hpp" +#include "src/buildtool/common/artifact_factory.hpp" +#include "src/buildtool/execution_api/common/execution_api.hpp" +#include "src/buildtool/execution_engine/dag/dag.hpp" +#include "src/buildtool/execution_engine/executor/executor.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "test/utils/test_env.hpp" + +using ApiFactory = std::function<IExecutionApi::Ptr()>; + +static inline void SetupConfig() { + auto info = RepositoryConfig::RepositoryInfo{ + FileRoot{"test/buildtool/execution_engine/executor"}}; + RepositoryConfig::Instance().SetInfo("", std::move(info)); +} + +static inline void RunBlobUpload(ApiFactory const& factory) { + SetupConfig(); + auto api = factory(); + std::string const blob = "test"; + CHECK(api->Upload(BlobContainer{ + {BazelBlob{ArtifactDigest{ComputeHash(blob), blob.size()}, blob}}})); +} + +[[nodiscard]] static inline auto GetTestDir() -> std::filesystem::path { + auto* tmp_dir = std::getenv("TEST_TMPDIR"); + if (tmp_dir != nullptr) { + return tmp_dir; + } + return FileSystemManager::GetCurrentDirectory() / + "test/buildtool/execution_engine/executor"; +} + +template <class Executor> +[[nodiscard]] static inline auto AddAndProcessTree(DependencyGraph* g, + Executor* runner, + Tree const& tree_desc) + -> std::optional<Artifact::ObjectInfo> { + REQUIRE(g->AddAction(tree_desc.Action())); + + // obtain tree action and tree artifact + auto const* tree_action = g->ActionNodeWithId(tree_desc.Id()); + REQUIRE_FALSE(tree_action == nullptr); + auto const* tree_artifact = g->ArtifactNodeWithId(tree_desc.Output().Id()); + REQUIRE_FALSE(tree_artifact == nullptr); + + // "run" tree action to produce tree artifact + REQUIRE(runner->Process(tree_action)); + + // read computed tree artifact info (digest + object type) + return tree_artifact->Content().Info(); +} + +static inline void RunHelloWorldCompilation(ApiFactory const& factory, + bool is_hermetic = true, + int expected_queued = 0, + int expected_cached = 0) { + using path = std::filesystem::path; + SetupConfig(); + auto const main_cpp_desc = + ArtifactDescription{path{"data/hello_world/main.cpp"}, ""}; + auto const main_cpp_id = main_cpp_desc.Id(); + std::string const make_hello_id = "make_hello"; + auto const make_hello_desc = ActionDescription{ + {"out/hello_world"}, + {}, + Action{make_hello_id, + {"c++", "src/main.cpp", "-o", "out/hello_world"}, + {{"PATH", "/bin:/usr/bin"}}}, + {{"src/main.cpp", main_cpp_desc}}}; + auto const exec_desc = + ArtifactDescription{make_hello_id, "out/hello_world"}; + auto const exec_id = exec_desc.Id(); + + DependencyGraph g; + CHECK(g.AddAction(make_hello_desc)); + CHECK(g.ArtifactNodeWithId(exec_id)->HasBuilderAction()); + + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + + // upload local artifacts + auto const* main_cpp_node = g.ArtifactNodeWithId(main_cpp_id); + CHECK(main_cpp_node != nullptr); + CHECK(runner.Process(main_cpp_node)); + + // process action + CHECK(runner.Process(g.ArtifactNodeWithId(exec_id)->BuilderActionNode())); + if (is_hermetic) { + CHECK(Statistics::Instance().ActionsQueuedCounter() == expected_queued); + CHECK(Statistics::Instance().ActionsCachedCounter() == expected_cached); + } + + auto tmpdir = GetTestDir(); + + // retrieve ALL artifacts + REQUIRE(FileSystemManager::CreateDirectory(tmpdir)); + for (auto const& artifact_id : g.ArtifactIdentifiers()) { + CHECK(api->RetrieveToPaths( + {*g.ArtifactNodeWithId(artifact_id)->Content().Info()}, + {(tmpdir / "output").string()})); + CHECK(FileSystemManager::IsFile(tmpdir / "output")); + REQUIRE(FileSystemManager::RemoveFile(tmpdir / "output")); + } +} + +static inline void RunGreeterCompilation(ApiFactory const& factory, + std::string const& greetcpp, + bool is_hermetic = true, + int expected_queued = 0, + int expected_cached = 0) { + using path = std::filesystem::path; + SetupConfig(); + auto const greet_hpp_desc = + ArtifactDescription{path{"data/greeter/greet.hpp"}, ""}; + auto const greet_hpp_id = greet_hpp_desc.Id(); + auto const greet_cpp_desc = + ArtifactDescription{path{"data/greeter"} / greetcpp, ""}; + auto const greet_cpp_id = greet_cpp_desc.Id(); + + std::string const compile_greet_id = "compile_greet"; + auto const compile_greet_desc = + ActionDescription{{"out/greet.o"}, + {}, + Action{compile_greet_id, + {"c++", + "-c", + "src/greet.cpp", + "-I", + "include", + "-o", + "out/greet.o"}, + {{"PATH", "/bin:/usr/bin"}}}, + {{"include/greet.hpp", greet_hpp_desc}, + {"src/greet.cpp", greet_cpp_desc}}}; + + auto const greet_o_desc = + ArtifactDescription{compile_greet_id, "out/greet.o"}; + auto const greet_o_id = greet_o_desc.Id(); + + std::string const make_lib_id = "make_lib"; + auto const make_lib_desc = ActionDescription{ + {"out/libgreet.a"}, + {}, + Action{make_lib_id, {"ar", "rcs", "out/libgreet.a", "greet.o"}, {}}, + {{"greet.o", greet_o_desc}}}; + + auto const main_cpp_desc = + ArtifactDescription{path{"data/greeter/main.cpp"}, ""}; + auto const main_cpp_id = main_cpp_desc.Id(); + + auto const libgreet_desc = + ArtifactDescription{make_lib_id, "out/libgreet.a"}; + auto const libgreet_id = libgreet_desc.Id(); + + std::string const make_exe_id = "make_exe"; + auto const make_exe_desc = + ActionDescription{{"out/greeter"}, + {}, + Action{make_exe_id, + {"c++", + "src/main.cpp", + "-I", + "include", + "-L", + "lib", + "-lgreet", + "-o", + "out/greeter"}, + {{"PATH", "/bin:/usr/bin"}}}, + {{"src/main.cpp", main_cpp_desc}, + {"include/greet.hpp", greet_hpp_desc}, + {"lib/libgreet.a", libgreet_desc}}}; + + auto const exec_id = ArtifactDescription(make_exe_id, "out/greeter").Id(); + + DependencyGraph g; + CHECK(g.Add({compile_greet_desc, make_lib_desc, make_exe_desc})); + + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + + // upload local artifacts + for (auto const& id : {greet_hpp_id, greet_cpp_id, main_cpp_id}) { + auto const* node = g.ArtifactNodeWithId(id); + CHECK(node != nullptr); + CHECK(runner.Process(node)); + } + + // process actions + CHECK( + runner.Process(g.ArtifactNodeWithId(greet_o_id)->BuilderActionNode())); + CHECK( + runner.Process(g.ArtifactNodeWithId(libgreet_id)->BuilderActionNode())); + CHECK(runner.Process(g.ArtifactNodeWithId(exec_id)->BuilderActionNode())); + if (is_hermetic) { + CHECK(Statistics::Instance().ActionsQueuedCounter() == expected_queued); + CHECK(Statistics::Instance().ActionsCachedCounter() == expected_cached); + } + + auto tmpdir = GetTestDir(); + + // retrieve ALL artifacts + REQUIRE(FileSystemManager::CreateDirectory(tmpdir)); + for (auto const& artifact_id : g.ArtifactIdentifiers()) { + CHECK(api->RetrieveToPaths( + {*g.ArtifactNodeWithId(artifact_id)->Content().Info()}, + {(tmpdir / "output").string()})); + CHECK(FileSystemManager::IsFile(tmpdir / "output")); + REQUIRE(FileSystemManager::RemoveFile(tmpdir / "output")); + } +} + +[[maybe_unused]] static void TestBlobUpload(ApiFactory const& factory) { + SetupConfig(); + // NOLINTNEXTLINE + RunBlobUpload(factory); +} + +[[maybe_unused]] static void TestHelloWorldCompilation( + ApiFactory const& factory, + bool is_hermetic = true) { + SetupConfig(); + // expecting 1 action queued, 0 results from cache + // NOLINTNEXTLINE + RunHelloWorldCompilation(factory, is_hermetic, 1, 0); + + SECTION("Running same compilation again") { + // expecting 2 actions queued, 1 result from cache + // NOLINTNEXTLINE + RunHelloWorldCompilation(factory, is_hermetic, 2, 1); + } +} + +[[maybe_unused]] static void TestGreeterCompilation(ApiFactory const& factory, + bool is_hermetic = true) { + SetupConfig(); + // expecting 3 action queued, 0 results from cache + // NOLINTNEXTLINE + RunGreeterCompilation(factory, "greet.cpp", is_hermetic, 3, 0); + + SECTION("Running same compilation again") { + // expecting 6 actions queued, 3 results from cache + // NOLINTNEXTLINE + RunGreeterCompilation(factory, "greet.cpp", is_hermetic, 6, 3); + } + + SECTION("Running modified compilation") { + // expecting 6 actions queued, 2 results from cache + // NOLINTNEXTLINE + RunGreeterCompilation(factory, "greet_mod.cpp", is_hermetic, 6, 2); + } +} + +static inline void TestUploadAndDownloadTrees(ApiFactory const& factory, + bool /*is_hermetic*/ = true, + int /*expected_queued*/ = 0, + int /*expected_cached*/ = 0) { + SetupConfig(); + auto tmpdir = GetTestDir(); + + auto foo = std::string{"foo"}; + auto bar = std::string{"bar"}; + auto foo_digest = ArtifactDigest{ComputeHash(foo), foo.size()}; + auto bar_digest = ArtifactDigest{ComputeHash(bar), bar.size()}; + + // upload blobs + auto api = factory(); + REQUIRE(api->Upload(BlobContainer{ + {BazelBlob{foo_digest, foo}, BazelBlob{bar_digest, bar}}})); + + // define known artifacts + auto foo_desc = ArtifactDescription{foo_digest, ObjectType::File}; + auto bar_desc = ArtifactDescription{bar_digest, ObjectType::File}; + + DependencyGraph g{}; + auto foo_id = g.AddArtifact(foo_desc); + auto bar_id = g.AddArtifact(bar_desc); + + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + REQUIRE(runner.Process(g.ArtifactNodeWithId(foo_id))); + REQUIRE(runner.Process(g.ArtifactNodeWithId(bar_id))); + + SECTION("Simple tree") { + auto tree_desc = Tree{{{"a", foo_desc}, {"b", bar_desc}}}; + auto tree_info = AddAndProcessTree(&g, &runner, tree_desc); + REQUIRE(tree_info); + CHECK(IsTreeObject(tree_info->type)); + + tmpdir /= "simple"; + CHECK(api->RetrieveToPaths({*tree_info}, {tmpdir.string()})); + CHECK(FileSystemManager::IsDirectory(tmpdir)); + CHECK(FileSystemManager::IsFile(tmpdir / "a")); + CHECK(FileSystemManager::IsFile(tmpdir / "b")); + CHECK(*FileSystemManager::ReadFile(tmpdir / "a") == "foo"); + CHECK(*FileSystemManager::ReadFile(tmpdir / "b") == "bar"); + REQUIRE(FileSystemManager::RemoveDirectory(tmpdir, true)); + } + + SECTION("Subdir in tree path") { + auto tree_desc = Tree{{{"a", foo_desc}, {"b/a", bar_desc}}}; + auto tree_info = AddAndProcessTree(&g, &runner, tree_desc); + REQUIRE(tree_info); + CHECK(IsTreeObject(tree_info->type)); + + tmpdir /= "subdir"; + CHECK(api->RetrieveToPaths({*tree_info}, {tmpdir.string()})); + CHECK(FileSystemManager::IsDirectory(tmpdir)); + CHECK(FileSystemManager::IsDirectory(tmpdir / "b")); + CHECK(FileSystemManager::IsFile(tmpdir / "a")); + CHECK(FileSystemManager::IsFile(tmpdir / "b" / "a")); + CHECK(*FileSystemManager::ReadFile(tmpdir / "a") == "foo"); + CHECK(*FileSystemManager::ReadFile(tmpdir / "b" / "a") == "bar"); + REQUIRE(FileSystemManager::RemoveDirectory(tmpdir, true)); + } + + SECTION("Nested trees") { + auto tree_desc_nested = Tree{{{"a", bar_desc}}}; + auto tree_desc_parent = + Tree{{{"a", foo_desc}, {"b", tree_desc_nested.Output()}}}; + + REQUIRE(AddAndProcessTree(&g, &runner, tree_desc_nested)); + auto tree_info = AddAndProcessTree(&g, &runner, tree_desc_parent); + REQUIRE(tree_info); + CHECK(IsTreeObject(tree_info->type)); + + tmpdir /= "nested"; + CHECK(api->RetrieveToPaths({*tree_info}, {tmpdir.string()})); + CHECK(FileSystemManager::IsDirectory(tmpdir)); + CHECK(FileSystemManager::IsDirectory(tmpdir / "b")); + CHECK(FileSystemManager::IsFile(tmpdir / "a")); + CHECK(FileSystemManager::IsFile(tmpdir / "b" / "a")); + CHECK(*FileSystemManager::ReadFile(tmpdir / "a") == "foo"); + CHECK(*FileSystemManager::ReadFile(tmpdir / "b" / "a") == "bar"); + REQUIRE(FileSystemManager::RemoveDirectory(tmpdir, true)); + } + + SECTION("Dot-path tree as action input") { + auto tree_desc = Tree{{{"a", foo_desc}, {"b/a", bar_desc}}}; + auto action_inputs = + ActionDescription::inputs_t{{".", tree_desc.Output()}}; + ActionDescription action_desc{ + {"a", "b/a"}, {}, Action{"action_id", {"echo"}, {}}, action_inputs}; + + REQUIRE(AddAndProcessTree(&g, &runner, tree_desc)); + REQUIRE(g.Add({action_desc})); + auto const* action_node = g.ActionNodeWithId("action_id"); + REQUIRE(runner.Process(action_node)); + + tmpdir /= "dotpath"; + std::vector<Artifact::ObjectInfo> infos{}; + std::vector<std::filesystem::path> paths{}; + for (auto const& [path, node] : action_node->OutputFiles()) { + paths.emplace_back(tmpdir / path); + infos.emplace_back(*node->Content().Info()); + } + + CHECK(api->RetrieveToPaths(infos, paths)); + CHECK(FileSystemManager::IsDirectory(tmpdir)); + CHECK(FileSystemManager::IsDirectory(tmpdir / "b")); + CHECK(FileSystemManager::IsFile(tmpdir / "a")); + CHECK(FileSystemManager::IsFile(tmpdir / "b" / "a")); + CHECK(*FileSystemManager::ReadFile(tmpdir / "a") == "foo"); + CHECK(*FileSystemManager::ReadFile(tmpdir / "b" / "a") == "bar"); + REQUIRE(FileSystemManager::RemoveDirectory(tmpdir, true)); + } + + SECTION("Dot-path non-tree as action input") { + auto action_inputs = ActionDescription::inputs_t{{".", foo_desc}}; + ActionDescription action_desc{ + {"foo"}, {}, Action{"action_id", {"echo"}, {}}, action_inputs}; + + REQUIRE(g.Add({action_desc})); + auto const* action_node = g.ActionNodeWithId("action_id"); + REQUIRE_FALSE(runner.Process(action_node)); + } +} + +static inline void TestRetrieveOutputDirectories(ApiFactory const& factory, + bool /*is_hermetic*/ = true, + int /*expected_queued*/ = 0, + int /*expected_cached*/ = 0) { + SetupConfig(); + auto tmpdir = GetTestDir(); + + auto const make_tree_id = std::string{"make_tree"}; + auto const* make_tree_cmd = + "mkdir -p baz/baz/\n" + "touch foo bar\n" + "touch baz/foo baz/bar\n" + "touch baz/baz/foo baz/baz/bar"; + + auto create_action = [&make_tree_id, make_tree_cmd]( + std::vector<std::string>&& out_files, + std::vector<std::string>&& out_dirs) { + return ActionDescription{std::move(out_files), + std::move(out_dirs), + Action{make_tree_id, + {"sh", "-c", make_tree_cmd}, + {{"PATH", "/bin:/usr/bin"}}}, + {}}; + }; + + SECTION("entire action output as directory") { + auto const make_tree_desc = create_action({}, {""}); + auto const root_desc = ArtifactDescription{make_tree_id, ""}; + + DependencyGraph g{}; + REQUIRE(g.AddAction(make_tree_desc)); + + auto const* action = g.ActionNodeWithId(make_tree_id); + REQUIRE_FALSE(action == nullptr); + auto const* root = g.ArtifactNodeWithId(root_desc.Id()); + REQUIRE_FALSE(root == nullptr); + + // run action + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + REQUIRE(runner.Process(action)); + + // read output + auto root_info = root->Content().Info(); + REQUIRE(root_info); + CHECK(IsTreeObject(root_info->type)); + + // retrieve ALL artifacts + auto tmpdir = GetTestDir() / "entire_output"; + REQUIRE(FileSystemManager::CreateDirectory(tmpdir)); + + REQUIRE(api->RetrieveToPaths({*root_info}, {tmpdir})); + CHECK(FileSystemManager::IsFile(tmpdir / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "bar")); + CHECK(FileSystemManager::IsDirectory(tmpdir / "baz")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "bar")); + CHECK(FileSystemManager::IsDirectory(tmpdir / "baz" / "baz")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "baz" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "baz" / "bar")); + } + + SECTION("disjoint files and directories") { + auto const make_tree_desc = create_action({"foo", "bar"}, {"baz"}); + auto const foo_desc = ArtifactDescription{make_tree_id, "foo"}; + auto const bar_desc = ArtifactDescription{make_tree_id, "bar"}; + auto const baz_desc = ArtifactDescription{make_tree_id, "baz"}; + + DependencyGraph g{}; + REQUIRE(g.AddAction(make_tree_desc)); + + auto const* action = g.ActionNodeWithId(make_tree_id); + REQUIRE_FALSE(action == nullptr); + auto const* foo = g.ArtifactNodeWithId(foo_desc.Id()); + REQUIRE_FALSE(foo == nullptr); + auto const* bar = g.ArtifactNodeWithId(bar_desc.Id()); + REQUIRE_FALSE(bar == nullptr); + auto const* baz = g.ArtifactNodeWithId(baz_desc.Id()); + REQUIRE_FALSE(baz == nullptr); + + // run action + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + REQUIRE(runner.Process(action)); + + // read output + auto foo_info = foo->Content().Info(); + REQUIRE(foo_info); + CHECK(IsFileObject(foo_info->type)); + + auto bar_info = bar->Content().Info(); + REQUIRE(bar_info); + CHECK(IsFileObject(bar_info->type)); + + auto baz_info = baz->Content().Info(); + REQUIRE(baz_info); + CHECK(IsTreeObject(baz_info->type)); + + // retrieve ALL artifacts + auto tmpdir = GetTestDir() / "disjoint"; + REQUIRE(FileSystemManager::CreateDirectory(tmpdir)); + + REQUIRE(api->RetrieveToPaths({*foo_info}, {tmpdir / "foo"})); + CHECK(FileSystemManager::IsFile(tmpdir / "foo")); + + REQUIRE(api->RetrieveToPaths({*bar_info}, {tmpdir / "bar"})); + CHECK(FileSystemManager::IsFile(tmpdir / "bar")); + + REQUIRE(api->RetrieveToPaths({*baz_info}, {tmpdir / "baz"})); + CHECK(FileSystemManager::IsDirectory(tmpdir / "baz")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "bar")); + CHECK(FileSystemManager::IsDirectory(tmpdir / "baz" / "baz")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "baz" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "baz" / "bar")); + } + + SECTION("nested files and directories") { + auto const make_tree_desc = + create_action({"foo", "baz/bar"}, {"", "baz/baz"}); + auto const root_desc = ArtifactDescription{make_tree_id, ""}; + auto const foo_desc = ArtifactDescription{make_tree_id, "foo"}; + auto const bar_desc = ArtifactDescription{make_tree_id, "baz/bar"}; + auto const baz_desc = ArtifactDescription{make_tree_id, "baz/baz"}; + + DependencyGraph g{}; + REQUIRE(g.AddAction(make_tree_desc)); + + auto const* action = g.ActionNodeWithId(make_tree_id); + REQUIRE_FALSE(action == nullptr); + auto const* root = g.ArtifactNodeWithId(root_desc.Id()); + REQUIRE_FALSE(root == nullptr); + auto const* foo = g.ArtifactNodeWithId(foo_desc.Id()); + REQUIRE_FALSE(foo == nullptr); + auto const* bar = g.ArtifactNodeWithId(bar_desc.Id()); + REQUIRE_FALSE(bar == nullptr); + auto const* baz = g.ArtifactNodeWithId(baz_desc.Id()); + REQUIRE_FALSE(baz == nullptr); + + // run action + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + REQUIRE(runner.Process(action)); + + // read output + auto root_info = root->Content().Info(); + REQUIRE(root_info); + CHECK(IsTreeObject(root_info->type)); + + auto foo_info = foo->Content().Info(); + REQUIRE(foo_info); + CHECK(IsFileObject(foo_info->type)); + + auto bar_info = bar->Content().Info(); + REQUIRE(bar_info); + CHECK(IsFileObject(bar_info->type)); + + auto baz_info = baz->Content().Info(); + REQUIRE(baz_info); + CHECK(IsTreeObject(baz_info->type)); + + // retrieve ALL artifacts + auto tmpdir = GetTestDir() / "baz"; + REQUIRE(FileSystemManager::CreateDirectory(tmpdir)); + + REQUIRE(api->RetrieveToPaths({*root_info}, {tmpdir / "root"})); + CHECK(FileSystemManager::IsDirectory(tmpdir / "root")); + CHECK(FileSystemManager::IsFile(tmpdir / "root" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "root" / "bar")); + CHECK(FileSystemManager::IsDirectory(tmpdir / "root" / "baz")); + CHECK(FileSystemManager::IsFile(tmpdir / "root" / "baz" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "root" / "baz" / "bar")); + CHECK(FileSystemManager::IsDirectory(tmpdir / "root" / "baz" / "baz")); + CHECK( + FileSystemManager::IsFile(tmpdir / "root" / "baz" / "baz" / "foo")); + CHECK( + FileSystemManager::IsFile(tmpdir / "root" / "baz" / "baz" / "bar")); + + REQUIRE(api->RetrieveToPaths({*foo_info}, {tmpdir / "foo"})); + CHECK(FileSystemManager::IsFile(tmpdir / "foo")); + + REQUIRE(api->RetrieveToPaths({*bar_info}, {tmpdir / "bar"})); + CHECK(FileSystemManager::IsFile(tmpdir / "bar")); + + REQUIRE(api->RetrieveToPaths({*baz_info}, {tmpdir / "baz"})); + CHECK(FileSystemManager::IsDirectory(tmpdir / "baz")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "foo")); + CHECK(FileSystemManager::IsFile(tmpdir / "baz" / "bar")); + } + + SECTION("non-existing outputs") { + SECTION("non-existing file") { + auto const make_tree_desc = create_action({"fool"}, {}); + auto const fool_desc = ArtifactDescription{make_tree_id, "fool"}; + + DependencyGraph g{}; + REQUIRE(g.AddAction(make_tree_desc)); + + auto const* action = g.ActionNodeWithId(make_tree_id); + REQUIRE_FALSE(action == nullptr); + auto const* fool = g.ArtifactNodeWithId(fool_desc.Id()); + REQUIRE_FALSE(fool == nullptr); + + // run action + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + CHECK_FALSE(runner.Process(action)); + } + + SECTION("non-existing directory") { + auto const make_tree_desc = create_action({"bazel"}, {}); + auto const bazel_desc = ArtifactDescription{make_tree_id, "bazel"}; + + DependencyGraph g{}; + REQUIRE(g.AddAction(make_tree_desc)); + + auto const* action = g.ActionNodeWithId(make_tree_id); + REQUIRE_FALSE(action == nullptr); + auto const* bazel = g.ArtifactNodeWithId(bazel_desc.Id()); + REQUIRE_FALSE(bazel == nullptr); + + // run action + auto api = factory(); + Executor runner{api.get(), ReadPlatformPropertiesFromEnv()}; + CHECK_FALSE(runner.Process(action)); + } + } +} + +#endif // INCLUDED_SRC_TEST_BUILDTOOL_EXECUTION_ENGINE_EXECUTOR_EXECUTOR_API_TEST_HPP diff --git a/test/buildtool/execution_engine/executor/executor_api_local.test.cpp b/test/buildtool/execution_engine/executor/executor_api_local.test.cpp new file mode 100755 index 00000000..955e1682 --- /dev/null +++ b/test/buildtool/execution_engine/executor/executor_api_local.test.cpp @@ -0,0 +1,36 @@ +#include "catch2/catch.hpp" +#include "src/buildtool/execution_api/local/local_api.hpp" +#include "src/buildtool/execution_api/remote/config.hpp" +#include "src/buildtool/execution_engine/executor/executor.hpp" +#include "test/buildtool/execution_engine/executor/executor_api.test.hpp" +#include "test/utils/hermeticity/local.hpp" + +TEST_CASE_METHOD(HermeticLocalTestFixture, + "Executor<LocalApi>: Upload blob", + "[executor]") { + TestBlobUpload([&] { return std::make_unique<LocalApi>(); }); +} + +TEST_CASE_METHOD(HermeticLocalTestFixture, + "Executor<LocalApi>: Compile hello world", + "[executor]") { + TestHelloWorldCompilation([&] { return std::make_unique<LocalApi>(); }); +} + +TEST_CASE_METHOD(HermeticLocalTestFixture, + "Executor<LocalApi>: Compile greeter", + "[executor]") { + TestGreeterCompilation([&] { return std::make_unique<LocalApi>(); }); +} + +TEST_CASE_METHOD(HermeticLocalTestFixture, + "Executor<LocalApi>: Upload and download trees", + "[executor]") { + TestUploadAndDownloadTrees([&] { return std::make_unique<LocalApi>(); }); +} + +TEST_CASE_METHOD(HermeticLocalTestFixture, + "Executor<LocalApi>: Retrieve output directories", + "[executor]") { + TestRetrieveOutputDirectories([&] { return std::make_unique<LocalApi>(); }); +} diff --git a/test/buildtool/execution_engine/executor/executor_api_remote_bazel.test.cpp b/test/buildtool/execution_engine/executor/executor_api_remote_bazel.test.cpp new file mode 100755 index 00000000..d6ad57b1 --- /dev/null +++ b/test/buildtool/execution_engine/executor/executor_api_remote_bazel.test.cpp @@ -0,0 +1,71 @@ +#include "catch2/catch.hpp" +#include "src/buildtool/execution_api/remote/bazel/bazel_api.hpp" +#include "src/buildtool/execution_api/remote/config.hpp" +#include "src/buildtool/execution_engine/executor/executor.hpp" +#include "test/buildtool/execution_engine/executor/executor_api.test.hpp" + +TEST_CASE("Executor<BazelApi>: Upload blob", "[executor]") { + ExecutionConfiguration config; + auto const& info = RemoteExecutionConfig::Instance(); + + TestBlobUpload([&] { + return BazelApi::Ptr{ + new BazelApi{"remote-execution", info.Host(), info.Port(), config}}; + }); +} + +TEST_CASE("Executor<BazelApi>: Compile hello world", "[executor]") { + ExecutionConfiguration config; + config.skip_cache_lookup = false; + + auto const& info = RemoteExecutionConfig::Instance(); + + TestHelloWorldCompilation( + [&] { + return BazelApi::Ptr{new BazelApi{ + "remote-execution", info.Host(), info.Port(), config}}; + }, + false /* not hermetic */); +} + +TEST_CASE("Executor<BazelApi>: Compile greeter", "[executor]") { + ExecutionConfiguration config; + config.skip_cache_lookup = false; + + auto const& info = RemoteExecutionConfig::Instance(); + + TestGreeterCompilation( + [&] { + return BazelApi::Ptr{new BazelApi{ + "remote-execution", info.Host(), info.Port(), config}}; + }, + false /* not hermetic */); +} + +TEST_CASE("Executor<BazelApi>: Upload and download trees", "[executor]") { + ExecutionConfiguration config; + config.skip_cache_lookup = false; + + auto const& info = RemoteExecutionConfig::Instance(); + + TestUploadAndDownloadTrees( + [&] { + return BazelApi::Ptr{new BazelApi{ + "remote-execution", info.Host(), info.Port(), config}}; + }, + false /* not hermetic */); +} + +TEST_CASE("Executor<BazelApi>: Retrieve output directories", "[executor]") { + ExecutionConfiguration config; + config.skip_cache_lookup = false; + + auto const& info = RemoteExecutionConfig::Instance(); + + TestRetrieveOutputDirectories( + [&] { + return BazelApi::Ptr{new BazelApi{ + "remote-execution", info.Host(), info.Port(), config}}; + }, + false /* not hermetic */); +} |