// 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. #ifndef INCLUDED_SRC_TEST_BUILDTOOL_GRAPH_GRAVERSER_GRAPH_TRAVERSER_TEST_HPP #define INCLUDED_SRC_TEST_BUILDTOOL_GRAPH_GRAVERSER_GRAPH_TRAVERSER_TEST_HPP #include #include #include #include #include #include #include #include #include #include "catch2/catch_test_macros.hpp" #include "gsl/gsl" #include "nlohmann/json.hpp" #include "src/buildtool/auth/authentication.hpp" #include "src/buildtool/common/cli.hpp" #include "src/buildtool/common/protocol_traits.hpp" #include "src/buildtool/common/remote/retry_config.hpp" #include "src/buildtool/common/repository_config.hpp" #include "src/buildtool/common/statistics.hpp" #include "src/buildtool/execution_api/common/api_bundle.hpp" #include "src/buildtool/execution_api/local/config.hpp" #include "src/buildtool/execution_api/local/context.hpp" #include "src/buildtool/execution_api/remote/config.hpp" #include "src/buildtool/execution_api/remote/context.hpp" #include "src/buildtool/execution_engine/executor/context.hpp" #include "src/buildtool/file_system/file_root.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/file_system/jsonfs.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/graph_traverser/graph_traverser.hpp" #include "src/buildtool/logging/log_level.hpp" #include "src/buildtool/logging/logger.hpp" #include "src/buildtool/progress_reporting/progress.hpp" #include "src/buildtool/storage/config.hpp" #include "src/buildtool/storage/storage.hpp" #include "src/utils/cpp/expected.hpp" #include "test/utils/hermeticity/test_hash_function_type.hpp" // NOLINTNEXTLINE(google-build-namespaces) namespace { class TestProject { public: struct CommandLineArguments { GraphTraverser::CommandLineArguments gtargs; nlohmann::json artifacts; std::filesystem::path graph_description; explicit CommandLineArguments( GraphTraverser::CommandLineArguments gtargs) : gtargs{std::move(std::move(gtargs))} {} }; // NOLINTNEXTLINE(modernize-pass-by-value) explicit TestProject(std::string const& example_name) : example_name_{example_name}, root_dir_{kWorkspacePrefix / example_name_} { SetupConfig(); } explicit TestProject(std::string&& example_name) : example_name_{std::move(example_name)}, root_dir_{kWorkspacePrefix / example_name_} { SetupConfig(); } /// \brief Get command line arguments parsing entry points file in /// data//, where /// takes "_entry_points" as default value auto CmdLineArgs(std::string const& entry_points_filename = kDefaultEntryPointsFileName) -> CommandLineArguments { auto const entry_points_file = root_dir_ / entry_points_filename; if (not FileSystemManager::IsFile(entry_points_file)) { Logger::Log( LogLevel::Error, "file with entry points for graph_traverser tests can not be " "found in path {}", entry_points_file.string()); std::exit(EXIT_FAILURE); } auto const entry_points_json = Json::ReadFile(entry_points_file); if (not entry_points_json.has_value()) { Logger::Log(LogLevel::Error, "can not read {} for graph_traverser tests", entry_points_file.string()); std::exit(EXIT_FAILURE); } return GenerateFromEntryPoints(*entry_points_json); } auto GetRepoConfig() -> RepositoryConfig* { return &repo_config_; } private: static inline std::filesystem::path const kOutputDirPrefix = FileSystemManager::GetCurrentDirectory() / "./tmp-"; static inline std::filesystem::path const kWorkspacePrefix = FileSystemManager::GetCurrentDirectory() / "test/buildtool/graph_traverser/data/"; static inline std::string const kDefaultEntryPointsFileName = "_entry_points"; std::string example_name_; std::filesystem::path root_dir_; RepositoryConfig repo_config_{}; void SetupConfig() { auto info = RepositoryConfig::RepositoryInfo{FileRoot{root_dir_}}; repo_config_.SetInfo("", std::move(info)); } auto GenerateFromEntryPoints(nlohmann::json const& entry_points) -> CommandLineArguments { static int id{}; GraphTraverser::CommandLineArguments gtargs{0, {}, {}, {}}; CommandLineArguments clargs{gtargs}; clargs.artifacts = entry_points; auto const comp_graph = root_dir_ / "graph_description_compatible"; if (not ProtocolTraits::IsNative( TestHashType::ReadFromEnvironment()) and FileSystemManager::Exists(comp_graph)) { clargs.graph_description = comp_graph; } else { clargs.graph_description = root_dir_ / "graph_description"; } clargs.gtargs.jobs = std::max(1U, std::thread::hardware_concurrency()); clargs.gtargs.stage = StageArguments{ kOutputDirPrefix / (example_name_ + std::to_string(id++))}; return clargs; } }; [[nodiscard]] inline auto CreateLocalExecConfig() noexcept -> LocalExecutionConfig { std::vector launcher{"env"}; auto* env_path = std::getenv("PATH"); if (env_path != nullptr) { launcher.emplace_back(std::string{"PATH="} + std::string{env_path}); } else { launcher.emplace_back("PATH=/bin:/usr/bin"); } LocalExecutionConfig::Builder builder; if (auto config = builder.SetLauncher(std::move(launcher)).Build()) { return *std::move(config); } Logger::Log(LogLevel::Error, "Failure setting the local launcher."); std::exit(EXIT_FAILURE); } } // namespace [[maybe_unused]] static void TestHelloWorldCopyMessage( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("hello_world_copy_message"); auto const local_exec_config = CreateLocalExecConfig(); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser const gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); auto const contents = FileSystemManager::ReadFile(result->output_paths.at(0)); CHECK(contents.has_value()); CHECK(contents == "Hello, World!\n"); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 2); CHECK(stats.ActionsCachedCounter() == 0); } SECTION("Executable is retrieved as executable") { auto const clargs_exec = p.CmdLineArgs("_entry_points_get_executable"); GraphTraverser const gt_get_exec{clargs_exec.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const exec_result = gt_get_exec.BuildAndStage( clargs_exec.graph_description, clargs_exec.artifacts); REQUIRE(exec_result); REQUIRE(exec_result->output_paths.size() == 1); auto const exec_path = exec_result->output_paths.at(0); CHECK(FileSystemManager::IsFile(exec_path)); CHECK(FileSystemManager::IsExecutable(exec_path)); CHECK(FileSystemManager::Type(exec_path) == ObjectType::Executable); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 3); // One more action queued CHECK(stats.ActionsCachedCounter() == 1); // But that action was cached } } } [[maybe_unused]] static void TestCopyLocalFile( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("copy_local_file"); auto const local_exec_config = CreateLocalExecConfig(); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser const gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 0); CHECK(stats.ActionsCachedCounter() == 0); } } [[maybe_unused]] static void TestSequencePrinterBuildLibraryOnly( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("sequence_printer_build_library_only"); auto const local_exec_config = CreateLocalExecConfig(); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser const gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); auto const clargs_full_build = p.CmdLineArgs("_entry_points_full_build"); GraphTraverser const gt_full_build{clargs_full_build.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const full_build_result = gt_full_build.BuildAndStage( clargs_full_build.graph_description, clargs_full_build.artifacts); REQUIRE(full_build_result); REQUIRE(full_build_result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(full_build_result->output_paths.at(0))); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 8); CHECK(stats.ActionsCachedCounter() == 3); } else { CHECK(stats.ActionsCachedCounter() > 0); } } [[maybe_unused]] static void TestHelloWorldWithKnownSource( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject full_hello_world("hello_world_copy_message"); auto const local_exec_config = CreateLocalExecConfig(); auto const clargs_update_cpp = full_hello_world.CmdLineArgs("_entry_points_upload_source"); Statistics stats{}; Progress progress{}; // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const full_apis = ApiBundle::Create( &local_context, &remote_context, full_hello_world.GetRepoConfig()); ExecutionContext const full_context{ .repo_config = full_hello_world.GetRepoConfig(), .apis = &full_apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser const gt_upload{clargs_update_cpp.gtargs, &full_context, [](auto /*done*/, auto /*cv*/) {}}; auto const cpp_result = gt_upload.BuildAndStage( clargs_update_cpp.graph_description, clargs_update_cpp.artifacts); REQUIRE(cpp_result); REQUIRE(cpp_result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(cpp_result->output_paths.at(0))); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 0); CHECK(stats.ActionsCachedCounter() == 0); } TestProject hello_world_known_cpp("hello_world_known_source"); auto const clargs = hello_world_known_cpp.CmdLineArgs(); auto const apis_known = ApiBundle::Create( &local_context, &remote_context, hello_world_known_cpp.GetRepoConfig()); ExecutionContext const context_known{ .repo_config = hello_world_known_cpp.GetRepoConfig(), .apis = &apis_known, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser const gt{ clargs.gtargs, &context_known, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 2); CHECK(stats.ActionsCachedCounter() == 0); } else { CHECK(stats.ActionsQueuedCounter() >= 2); } } static void TestBlobsUploadedAndUsed( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("use_uploaded_blobs"); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; auto const local_exec_config = CreateLocalExecConfig(); // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); auto const contents = FileSystemManager::ReadFile(result->output_paths.at(0)); CHECK(contents.has_value()); CHECK(contents == "this is a test to check if blobs are uploaded"); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 1); CHECK(stats.ActionsCachedCounter() == 0); } else { CHECK(stats.ActionsQueuedCounter() >= 1); } } static void TestEnvironmentVariablesSetAndUsed( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("use_env_variables"); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; auto const local_exec_config = CreateLocalExecConfig(); // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); auto const contents = FileSystemManager::ReadFile(result->output_paths.at(0)); CHECK(contents.has_value()); CHECK(contents == "content from environment variable"); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 1); CHECK(stats.ActionsCachedCounter() == 0); } else { CHECK(stats.ActionsQueuedCounter() >= 1); } } static void TestTreesUsed( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("use_trees"); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; auto const local_exec_config = CreateLocalExecConfig(); // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); auto const contents = FileSystemManager::ReadFile(result->output_paths.at(0)); CHECK(contents.has_value()); CHECK(contents == "this is a test to check if blobs are uploaded"); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 2); CHECK(stats.ActionsCachedCounter() == 0); } else { CHECK(stats.ActionsQueuedCounter() >= 2); } } static void TestNestedTreesUsed( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool is_hermetic = true) { TestProject p("use_nested_trees"); auto const clargs = p.CmdLineArgs(); Statistics stats{}; Progress progress{}; auto const local_exec_config = CreateLocalExecConfig(); // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; GraphTraverser gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); CHECK(FileSystemManager::IsFile(result->output_paths.at(0))); auto const contents = FileSystemManager::ReadFile(result->output_paths.at(0)); CHECK(contents.has_value()); CHECK(contents == "this is a test to check if blobs are uploaded"); if (is_hermetic) { CHECK(stats.ActionsQueuedCounter() == 1); CHECK(stats.ActionsCachedCounter() == 0); } else { CHECK(stats.ActionsQueuedCounter() >= 1); } } static void TestFlakyHelloWorldDetected( StorageConfig const& storage_config, Storage const& storage, gsl::not_null const& auth, gsl::not_null const& remote_config, bool /*is_hermetic*/ = true) { TestProject p("flaky_hello_world"); Statistics stats{}; Progress progress{}; auto const local_exec_config = CreateLocalExecConfig(); // pack the local context instances to be passed to ApiBundle LocalContext const local_context{.exec_config = &local_exec_config, .storage_config = &storage_config, .storage = &storage}; RetryConfig retry_config{}; // default retry config // pack the remote context instances to be passed to ApiBundle RemoteContext const remote_context{.auth = auth, .retry_config = &retry_config, .exec_config = remote_config}; auto const apis = ApiBundle::Create(&local_context, &remote_context, p.GetRepoConfig()); ExecutionContext const exec_context{.repo_config = p.GetRepoConfig(), .apis = &apis, .remote_context = &remote_context, .statistics = &stats, .progress = &progress}; { auto clargs = p.CmdLineArgs("_entry_points_ctimes"); GraphTraverser const gt{ clargs.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; auto const result = gt.BuildAndStage(clargs.graph_description, clargs.artifacts); REQUIRE(result); REQUIRE(result->output_paths.size() == 1); } using namespace std::chrono_literals; std::this_thread::sleep_for(1s); // make_exe[flaky]->make_output[miss] auto clargs_output = p.CmdLineArgs(); clargs_output.gtargs.rebuild = RebuildArguments{}; GraphTraverser const gt_output{ clargs_output.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; REQUIRE(gt_output.BuildAndStage(clargs_output.graph_description, clargs_output.artifacts)); CHECK(stats.ActionsFlakyCounter() == 1); CHECK(stats.RebuiltActionComparedCounter() == 1); CHECK(stats.RebuiltActionMissingCounter() == 1); stats.Reset(); // make_exe[flaky]->make_output[miss]->strip_time [miss] auto clargs_stripped = p.CmdLineArgs("_entry_points_stripped"); clargs_stripped.gtargs.rebuild = RebuildArguments{}; GraphTraverser const gt_stripped{clargs_stripped.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; REQUIRE(gt_stripped.BuildAndStage(clargs_stripped.graph_description, clargs_stripped.artifacts)); CHECK(stats.ActionsFlakyCounter() == 1); CHECK(stats.RebuiltActionComparedCounter() == 1); CHECK(stats.RebuiltActionMissingCounter() == 2); stats.Reset(); // make_exe[flaky]->make_output[miss]->strip_time[miss]->list_ctimes [flaky] auto clargs_ctimes = p.CmdLineArgs("_entry_points_ctimes"); clargs_ctimes.gtargs.rebuild = RebuildArguments{}; GraphTraverser const gt_ctimes{ clargs_ctimes.gtargs, &exec_context, [](auto /*done*/, auto /*cv*/) {}}; REQUIRE(gt_ctimes.BuildAndStage(clargs_ctimes.graph_description, clargs_ctimes.artifacts)); CHECK(stats.ActionsFlakyCounter() == 2); CHECK(stats.RebuiltActionComparedCounter() == 2); CHECK(stats.RebuiltActionMissingCounter() == 2); } #endif // INCLUDED_SRC_TEST_BUILDTOOL_GRAPH_GRAVERSER_GRAPH_TRAVERSER_TEST_HPP