From 582b2c04dd087cff0dc32ff4d268d71fee159d29 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Wed, 29 May 2024 01:50:57 +0200 Subject: [PATCH] Allow GoogleTest tests to locate their executable path. (#3992) This also factors out the code for doing this location from the driver to a tiny helper library. The motivation for this is letting tests locate data files like the prelude or other files needed by the toolchain more easily. A subsequent PR will use this heavily in the Clang runner and related logic for example. --- common/BUILD | 21 ++++++++++++++ common/exe_path.cpp | 23 ++++++++++++++++ common/exe_path.h | 21 ++++++++++++++ common/exe_path_test.cpp | 47 ++++++++++++++++++++++++++++++++ testing/base/BUILD | 15 ++++++++++ testing/base/gtest_main.cpp | 23 ++++++++++++++++ testing/base/gtest_main.h | 20 ++++++++++++++ testing/base/gtest_main_test.cpp | 25 +++++++++++++++++ toolchain/driver/BUILD | 1 + toolchain/driver/driver_main.cpp | 12 ++------ 10 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 common/exe_path.cpp create mode 100644 common/exe_path.h create mode 100644 common/exe_path_test.cpp create mode 100644 testing/base/gtest_main.h create mode 100644 testing/base/gtest_main_test.cpp diff --git a/common/BUILD b/common/BUILD index fcf314a6bc89..debe86d58a84 100644 --- a/common/BUILD +++ b/common/BUILD @@ -123,6 +123,27 @@ cc_test( ], ) +cc_library( + name = "exe_path", + srcs = ["exe_path.cpp"], + hdrs = ["exe_path.h"], + deps = [ + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "exe_path_test", + size = "small", + srcs = ["exe_path_test.cpp"], + deps = [ + ":exe_path", + "//testing/base:gtest_main", + "@googletest//:gtest", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "hashing", srcs = ["hashing.cpp"], diff --git a/common/exe_path.cpp b/common/exe_path.cpp new file mode 100644 index 000000000000..bd81dc7f9660 --- /dev/null +++ b/common/exe_path.cpp @@ -0,0 +1,23 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "common/exe_path.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Program.h" + +namespace Carbon { + +auto FindExecutablePath(llvm::StringRef argv0) -> std::string { + if (!llvm::sys::fs::exists(argv0)) { + if (llvm::ErrorOr path = llvm::sys::findProgramByName(argv0)) { + return std::move(*path); + } + } + + return argv0.str(); +} + +} // namespace Carbon diff --git a/common/exe_path.h b/common/exe_path.h new file mode 100644 index 000000000000..65685baac062 --- /dev/null +++ b/common/exe_path.h @@ -0,0 +1,21 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_COMMON_EXE_PATH_H_ +#define CARBON_COMMON_EXE_PATH_H_ + +#include "llvm/ADT/StringRef.h" + +namespace Carbon { + +// Computes the executable path for the given `argv[0]` value form `main`. +// +// A simplistic approach -- if the provided string isn't already a valid path, +// we look it up in the PATH environment variable. Doesn't resolve any symlinks +// and if it fails, simply returns the provided `argv0`. +auto FindExecutablePath(llvm::StringRef argv0) -> std::string; + +} // namespace Carbon + +#endif // CARBON_COMMON_EXE_PATH_H_ diff --git a/common/exe_path_test.cpp b/common/exe_path_test.cpp new file mode 100644 index 000000000000..32781b24c93a --- /dev/null +++ b/common/exe_path_test.cpp @@ -0,0 +1,47 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "common/exe_path.h" + +#include + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +namespace Carbon { +namespace { + +TEST(ExePath, FailureFallback) { + llvm::SmallString<128> path = llvm::StringRef(getenv("TEST_TMPDIR")); + llvm::sys::path::append(path, "non_existant_binary"); + std::string exe_path = FindExecutablePath(path); + EXPECT_EQ(path, exe_path); +} + +TEST(ExePath, File) { + llvm::SmallString<128> path = llvm::StringRef(getenv("TEST_TMPDIR")); + llvm::sys::path::append(path, "test_binary"); + int fd = -1; + std::error_code ec = llvm::sys::fs::openFileForWrite(path, fd); + ASSERT_TRUE(!ec) << "Error code: " << ec; + close(fd); + + std::string exe_path = FindExecutablePath(path); + EXPECT_EQ(path, exe_path); +} + +TEST(ExePath, PathLookup) { + // TODO: This is not likely to work well on Windows (outside of WSL). But some + // of that may be hidden by Bazel's test environment. Regardless, we should + // revisit this when we have good coverage of Windows build with something + // appropriate for that platform. + std::string exe_path = FindExecutablePath("bash"); + EXPECT_NE(exe_path, "bash"); + EXPECT_TRUE(llvm::sys::fs::exists(exe_path)); + EXPECT_TRUE(llvm::sys::fs::can_execute(exe_path)); +} + +} // namespace +} // namespace Carbon diff --git a/testing/base/BUILD b/testing/base/BUILD index d7c33c46838c..3e6f9c8eba3c 100644 --- a/testing/base/BUILD +++ b/testing/base/BUILD @@ -19,9 +19,24 @@ cc_library( name = "gtest_main", testonly = 1, srcs = ["gtest_main.cpp"], + hdrs = ["gtest_main.h"], deps = [ + "//common:check", + "//common:exe_path", "//common:init_llvm", "@googletest//:gtest", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "gtest_main_test", + size = "small", + srcs = ["gtest_main_test.cpp"], + deps = [ + ":gtest_main", + "@googletest//:gtest", + "@llvm-project//llvm:Support", ], ) diff --git a/testing/base/gtest_main.cpp b/testing/base/gtest_main.cpp index b7b4ec2c652d..e56b62976002 100644 --- a/testing/base/gtest_main.cpp +++ b/testing/base/gtest_main.cpp @@ -2,11 +2,34 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include "testing/base/gtest_main.h" + #include +#include + +#include "common/check.h" +#include "common/exe_path.h" #include "common/init_llvm.h" +static bool after_main = false; +static llvm::StringRef exe_path; + +namespace Carbon::Testing { + +auto GetTestExePath() -> llvm::StringRef { + CARBON_CHECK(after_main) + << "Must not query the executable path until after `main` is entered!"; + return exe_path; +} + +} // namespace Carbon::Testing + auto main(int argc, char** argv) -> int { + std::string exe_path_storage = Carbon::FindExecutablePath(argv[0]); + exe_path = exe_path_storage; + after_main = true; + Carbon::InitLLVM init_llvm(argc, argv); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/testing/base/gtest_main.h b/testing/base/gtest_main.h new file mode 100644 index 000000000000..5aa3b72b6f68 --- /dev/null +++ b/testing/base/gtest_main.h @@ -0,0 +1,20 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TESTING_BASE_GTEST_MAIN_H_ +#define CARBON_TESTING_BASE_GTEST_MAIN_H_ + +#include "llvm/ADT/StringRef.h" + +// When using the Carbon `main` function for GoogleTest, we export some extra +// information about the test binary that can be accessed with this header. + +namespace Carbon::Testing { + +// The executable path of the test binary. +auto GetTestExePath() -> llvm::StringRef; + +} // namespace Carbon::Testing + +#endif // CARBON_TESTING_BASE_GTEST_MAIN_H_ diff --git a/testing/base/gtest_main_test.cpp b/testing/base/gtest_main_test.cpp new file mode 100644 index 000000000000..0a72b6706aa8 --- /dev/null +++ b/testing/base/gtest_main_test.cpp @@ -0,0 +1,25 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "testing/base/gtest_main.h" + +#include +#include + +#include "llvm/Support/FileSystem.h" + +namespace Carbon::Testing { +namespace { + +using ::testing::StrNe; + +TEST(TestExePathTest, Test) { + llvm::StringRef exe_path = GetTestExePath(); + EXPECT_THAT(exe_path, StrNe("")); + EXPECT_TRUE(llvm::sys::fs::exists(exe_path)); + EXPECT_TRUE(llvm::sys::fs::can_execute(exe_path)); +} + +} // namespace +} // namespace Carbon::Testing diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index b0d41362611a..08d6fea62a99 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -114,6 +114,7 @@ cc_binary( ":driver", "//common:all_llvm_targets", "//common:bazel_working_dir", + "//common:exe_path", "//common:init_llvm", "@llvm-project//llvm:Support", ], diff --git a/toolchain/driver/driver_main.cpp b/toolchain/driver/driver_main.cpp index d3ac278a83b5..042357568547 100644 --- a/toolchain/driver/driver_main.cpp +++ b/toolchain/driver/driver_main.cpp @@ -5,11 +5,11 @@ #include #include "common/bazel_working_dir.h" +#include "common/exe_path.h" #include "common/init_llvm.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Path.h" -#include "llvm/Support/Program.h" #include "toolchain/driver/driver.h" auto main(int argc, char** argv) -> int { @@ -19,15 +19,7 @@ auto main(int argc, char** argv) -> int { return EXIT_FAILURE; } - // Find the executable without resolving symlinks. Do a PATH lookup if argv[0] - // isn't a valid path. - llvm::SmallString<128> exe_path(argv[0]); - if (!llvm::sys::fs::exists(exe_path)) { - if (llvm::ErrorOr path = - llvm::sys::findProgramByName(exe_path)) { - exe_path = *path; - } - } + std::string exe_path = Carbon::FindExecutablePath(argv[0]); Carbon::SetWorkingDirForBazel();