Copybara Merge: https://github.com/bazelbuild/rules_cc/pull/263
BEGIN_PUBLIC
Copybara import of the project:
--
82e44f3c472981bd13e86074c9ebd82974280030 by Fabian Meumertzheim <fabian@meumertzhe.im>:
Copy over runfiles library from Bazel
The runfiles library can be maintained independently of Bazel releases and bazel_tools can refer to it via an alias.
--
885d333d5565b210c1abc120eeee8e84c14e186f by Fabian Meumertzheim <fabian@meumertzhe.im>:
Switch to new include path and namespace
--
b8cd815963ecb00c826eed56461fd01d771306a1 by Fabian Meumertzheim <fabian@meumertzhe.im>:
Fix CI
--
41b7d48ed80d396c0e98b039a6a078366f21f9dc by Fabian Meumertzheim <fabian@meumertzhe.im>:
Silence warnings
--
b97a30e8bbb4eed94f024171289f28f9d78c4862 by Fabian Meumertzheim <fabian@meumertzhe.im>:
Remove per_file_copt
END_PUBLIC
PiperOrigin-RevId: 697942902
Change-Id: I10a6c9baf0464722fca026db99cf572acfd159f1
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 0000000..a244d62
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,2 @@
+# Required by abseil-cpp.
+common --cxxopt=-std=c++14
diff --git a/.gitignore b/.gitignore
index 0d4fed2..5a364af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
bazel-*
MODULE.bazel.lock
+/.ijwb/
+/.clwb/
diff --git a/MODULE.bazel b/MODULE.bazel
index 123e24d..01b6d8e 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -15,5 +15,6 @@
register_toolchains("@local_config_cc_toolchains//:all")
bazel_dep(name = "rules_shell", version = "0.2.0", dev_dependency = True)
+bazel_dep(name = "googletest", version = "1.15.2", dev_dependency = True)
bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True)
bazel_dep(name = "stardoc", version = "0.7.0", dev_dependency = True)
diff --git a/WORKSPACE b/WORKSPACE
index ce5112f..ce6d15d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -50,3 +50,10 @@
strip_prefix = "protobuf-27.0",
url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.tar.gz",
)
+
+http_archive(
+ name = "googletest",
+ integrity = "sha256-e0K01u1IgQxTYsJloX+uvpDcI3PIheUhZDnTeSfwKSY=",
+ strip_prefix = "googletest-1.15.2",
+ url = "https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz",
+)
diff --git a/cc/runfiles/BUILD b/cc/runfiles/BUILD
index 887e1f2..6a81a7c 100644
--- a/cc/runfiles/BUILD
+++ b/cc/runfiles/BUILD
@@ -1,7 +1,12 @@
+load("//cc:cc_library.bzl", "cc_library")
+
licenses(["notice"])
-alias(
+cc_library(
name = "runfiles",
- actual = "@bazel_tools//tools/cpp/runfiles",
+ srcs = ["runfiles.cc"],
+ hdrs = ["runfiles.h"],
+ include_prefix = "rules_cc/cc/runfiles",
+ strip_include_prefix = ".",
visibility = ["//visibility:public"],
)
diff --git a/cc/runfiles/runfiles.cc b/cc/runfiles/runfiles.cc
new file mode 100644
index 0000000..f580163
--- /dev/null
+++ b/cc/runfiles/runfiles.cc
@@ -0,0 +1,482 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+#include "rules_cc/cc/runfiles/runfiles.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#else // not _WIN32
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif // _WIN32
+
+#include <fstream>
+#include <functional>
+#include <map>
+#include <sstream>
+#include <vector>
+#include <utility>
+
+#ifdef _WIN32
+#include <memory>
+#endif // _WIN32
+
+namespace rules_cc {
+namespace cc {
+namespace runfiles {
+
+using std::function;
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace {
+
+bool starts_with(const string& s, const char* prefix) {
+ if (!prefix || !*prefix) {
+ return true;
+ }
+ if (s.empty()) {
+ return false;
+ }
+ return s.find(prefix) == 0;
+}
+
+bool contains(const string& s, const char* substr) {
+ if (!substr || !*substr) {
+ return true;
+ }
+ if (s.empty()) {
+ return false;
+ }
+ return s.find(substr) != string::npos;
+}
+
+bool ends_with(const string& s, const string& suffix) {
+ if (suffix.empty()) {
+ return true;
+ }
+ if (s.empty()) {
+ return false;
+ }
+ return s.rfind(suffix) == s.size() - suffix.size();
+}
+
+bool IsReadableFile(const string& path) {
+ return std::ifstream(path).is_open();
+}
+
+bool IsDirectory(const string& path) {
+#ifdef _WIN32
+ DWORD attrs = GetFileAttributesA(path.c_str());
+ return (attrs != INVALID_FILE_ATTRIBUTES) &&
+ (attrs & FILE_ATTRIBUTE_DIRECTORY);
+#else
+ struct stat buf;
+ return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode);
+#endif
+}
+
+bool PathsFrom(const std::string& argv0, std::string runfiles_manifest_file,
+ std::string runfiles_dir, std::string* out_manifest,
+ std::string* out_directory);
+
+bool PathsFrom(const std::string& argv0, std::string runfiles_manifest_file,
+ std::string runfiles_dir,
+ std::function<bool(const std::string&)> is_runfiles_manifest,
+ std::function<bool(const std::string&)> is_runfiles_directory,
+ std::string* out_manifest, std::string* out_directory);
+
+bool ParseManifest(const string& path, map<string, string>* result,
+ string* error);
+bool ParseRepoMapping(const string& path,
+ map<pair<string, string>, string>* result, string* error);
+
+} // namespace
+
+Runfiles* Runfiles::Create(const string& argv0,
+ const string& runfiles_manifest_file,
+ const string& runfiles_dir,
+ const string& source_repository, string* error) {
+ string manifest, directory;
+ if (!PathsFrom(argv0, runfiles_manifest_file, runfiles_dir, &manifest,
+ &directory)) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): cannot find runfiles (argv0=\"" << argv0 << "\")";
+ *error = err.str();
+ }
+ return nullptr;
+ }
+
+ vector<pair<string, string> > envvars = {
+ {"RUNFILES_MANIFEST_FILE", manifest},
+ {"RUNFILES_DIR", directory},
+ // TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
+ // pick up RUNFILES_DIR.
+ {"JAVA_RUNFILES", directory}};
+
+ map<string, string> runfiles;
+ if (!manifest.empty()) {
+ if (!ParseManifest(manifest, &runfiles, error)) {
+ return nullptr;
+ }
+ }
+
+ map<pair<string, string>, string> mapping;
+ if (!ParseRepoMapping(
+ RlocationUnchecked("_repo_mapping", runfiles, directory), &mapping,
+ error)) {
+ return nullptr;
+ }
+
+ return new Runfiles(std::move(runfiles), std::move(directory),
+ std::move(mapping), std::move(envvars),
+ string(source_repository));
+}
+
+bool IsAbsolute(const string& path) {
+ if (path.empty()) {
+ return false;
+ }
+ char c = path.front();
+ return (c == '/' && (path.size() < 2 || path[1] != '/')) ||
+ (path.size() >= 3 &&
+ ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) &&
+ path[1] == ':' && (path[2] == '\\' || path[2] == '/'));
+}
+
+string GetEnv(const string& key) {
+#ifdef _WIN32
+ DWORD size = ::GetEnvironmentVariableA(key.c_str(), nullptr, 0);
+ if (size == 0) {
+ return string(); // unset or empty envvar
+ }
+ std::unique_ptr<char[]> value(new char[size]);
+ ::GetEnvironmentVariableA(key.c_str(), value.get(), size);
+ return value.get();
+#else
+ char* result = getenv(key.c_str());
+ return (result == nullptr) ? string() : string(result);
+#endif
+}
+
+// Replaces \s, \n, and \b with their respective characters.
+string Unescape(const string& path) {
+ string result;
+ result.reserve(path.size());
+ for (size_t i = 0; i < path.size(); ++i) {
+ if (path[i] == '\\' && i + 1 < path.size()) {
+ switch (path[i + 1]) {
+ case 's': {
+ result.push_back(' ');
+ break;
+ }
+ case 'n': {
+ result.push_back('\n');
+ break;
+ }
+ case 'b': {
+ result.push_back('\\');
+ break;
+ }
+ default: {
+ result.push_back(path[i]);
+ result.push_back(path[i + 1]);
+ break;
+ }
+ }
+ ++i;
+ } else {
+ result.push_back(path[i]);
+ }
+ }
+ return result;
+}
+
+string Runfiles::Rlocation(const string& path) const {
+ return Rlocation(path, source_repository_);
+}
+
+string Runfiles::Rlocation(const string& path,
+ const string& source_repo) const {
+ if (path.empty() || starts_with(path, "../") || contains(path, "/..") ||
+ starts_with(path, "./") || contains(path, "/./") ||
+ ends_with(path, "/.") || contains(path, "//")) {
+ return string();
+ }
+ if (IsAbsolute(path)) {
+ return path;
+ }
+
+ string::size_type first_slash = path.find_first_of('/');
+ if (first_slash == string::npos) {
+ return RlocationUnchecked(path, runfiles_map_, directory_);
+ }
+ string target_apparent = path.substr(0, first_slash);
+ auto target =
+ repo_mapping_.find(std::make_pair(source_repo, target_apparent));
+ if (target == repo_mapping_.cend()) {
+ return RlocationUnchecked(path, runfiles_map_, directory_);
+ }
+ return RlocationUnchecked(target->second + path.substr(first_slash),
+ runfiles_map_, directory_);
+}
+
+string Runfiles::RlocationUnchecked(const string& path,
+ const map<string, string>& runfiles_map,
+ const string& directory) {
+ const auto exact_match = runfiles_map.find(path);
+ if (exact_match != runfiles_map.end()) {
+ return exact_match->second;
+ }
+ if (!runfiles_map.empty()) {
+ // If path references a runfile that lies under a directory that itself is a
+ // runfile, then only the directory is listed in the manifest. Look up all
+ // prefixes of path in the manifest and append the relative path from the
+ // prefix to the looked up path.
+ std::size_t prefix_end = path.size();
+ while ((prefix_end = path.find_last_of('/', prefix_end - 1)) !=
+ string::npos) {
+ const string prefix = path.substr(0, prefix_end);
+ const auto prefix_match = runfiles_map.find(prefix);
+ if (prefix_match != runfiles_map.end()) {
+ return prefix_match->second + "/" + path.substr(prefix_end + 1);
+ }
+ }
+ }
+ if (!directory.empty()) {
+ return directory + "/" + path;
+ }
+ return "";
+}
+
+namespace {
+
+bool ParseManifest(const string& path, map<string, string>* result,
+ string* error) {
+ std::ifstream stm(path);
+ if (!stm.is_open()) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): cannot open runfiles manifest \"" << path << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+ string line;
+ std::getline(stm, line);
+ size_t line_count = 1;
+ while (!line.empty()) {
+ std::string source;
+ std::string target;
+ if (line[0] == ' ') {
+ // The link path contains escape sequences for spaces and backslashes.
+ string::size_type idx = line.find(' ', 1);
+ if (idx == string::npos) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): bad runfiles manifest entry in \"" << path << "\" line #"
+ << line_count << ": \"" << line << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+ source = Unescape(line.substr(1, idx - 1));
+ target = Unescape(line.substr(idx + 1));
+ } else {
+ string::size_type idx = line.find(' ');
+ if (idx == string::npos) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): bad runfiles manifest entry in \"" << path << "\" line #"
+ << line_count << ": \"" << line << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+ source = line.substr(0, idx);
+ target = line.substr(idx + 1);
+ }
+ (*result)[source] = target;
+ std::getline(stm, line);
+ ++line_count;
+ }
+ return true;
+}
+
+bool ParseRepoMapping(const string& path,
+ map<pair<string, string>, string>* result,
+ string* error) {
+ std::ifstream stm(path);
+ if (!stm.is_open()) {
+ return true;
+ }
+ string line;
+ std::getline(stm, line);
+ size_t line_count = 1;
+ while (!line.empty()) {
+ string::size_type first_comma = line.find_first_of(',');
+ if (first_comma == string::npos) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): bad repository mapping entry in \"" << path << "\" line #"
+ << line_count << ": \"" << line << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+ string::size_type second_comma = line.find_first_of(',', first_comma + 1);
+ if (second_comma == string::npos) {
+ if (error) {
+ std::ostringstream err;
+ err << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): bad repository mapping entry in \"" << path << "\" line #"
+ << line_count << ": \"" << line << "\"";
+ *error = err.str();
+ }
+ return false;
+ }
+
+ string source = line.substr(0, first_comma);
+ string target_apparent =
+ line.substr(first_comma + 1, second_comma - (first_comma + 1));
+ string target = line.substr(second_comma + 1);
+
+ (*result)[std::make_pair(source, target_apparent)] = target;
+ std::getline(stm, line);
+ ++line_count;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace testing {
+
+bool TestOnly_PathsFrom(const string& argv0, string mf, string dir,
+ function<bool(const string&)> is_runfiles_manifest,
+ function<bool(const string&)> is_runfiles_directory,
+ string* out_manifest, string* out_directory) {
+ return PathsFrom(argv0, mf, dir, is_runfiles_manifest, is_runfiles_directory,
+ out_manifest, out_directory);
+}
+
+bool TestOnly_IsAbsolute(const string& path) { return IsAbsolute(path); }
+
+} // namespace testing
+
+Runfiles* Runfiles::Create(const std::string& argv0,
+ const std::string& runfiles_manifest_file,
+ const std::string& runfiles_dir,
+ std::string* error) {
+ return Runfiles::Create(argv0, runfiles_manifest_file, runfiles_dir, "",
+ error);
+}
+
+Runfiles* Runfiles::Create(const string& argv0, const string& source_repository,
+ string* error) {
+ return Runfiles::Create(argv0, GetEnv("RUNFILES_MANIFEST_FILE"),
+ GetEnv("RUNFILES_DIR"), source_repository, error);
+}
+
+Runfiles* Runfiles::Create(const string& argv0, string* error) {
+ return Runfiles::Create(argv0, "", error);
+}
+
+Runfiles* Runfiles::CreateForTest(const string& source_repository,
+ std::string* error) {
+ return Runfiles::Create(std::string(), GetEnv("RUNFILES_MANIFEST_FILE"),
+ GetEnv("TEST_SRCDIR"), source_repository, error);
+}
+
+Runfiles* Runfiles::CreateForTest(std::string* error) {
+ return Runfiles::CreateForTest("", error);
+}
+
+namespace {
+
+bool PathsFrom(const string& argv0, string mf, string dir, string* out_manifest,
+ string* out_directory) {
+ return PathsFrom(
+ argv0, mf, dir, [](const string& path) { return IsReadableFile(path); },
+ [](const string& path) { return IsDirectory(path); }, out_manifest,
+ out_directory);
+}
+
+bool PathsFrom(const string& argv0, string mf, string dir,
+ function<bool(const string&)> is_runfiles_manifest,
+ function<bool(const string&)> is_runfiles_directory,
+ string* out_manifest, string* out_directory) {
+ out_manifest->clear();
+ out_directory->clear();
+
+ bool mfValid = is_runfiles_manifest(mf);
+ bool dirValid = is_runfiles_directory(dir);
+
+ if (!argv0.empty() && !mfValid && !dirValid) {
+ mf = argv0 + ".runfiles/MANIFEST";
+ dir = argv0 + ".runfiles";
+ mfValid = is_runfiles_manifest(mf);
+ dirValid = is_runfiles_directory(dir);
+ if (!mfValid) {
+ mf = argv0 + ".runfiles_manifest";
+ mfValid = is_runfiles_manifest(mf);
+ }
+ }
+
+ if (!mfValid && !dirValid) {
+ return false;
+ }
+
+ if (!mfValid) {
+ mf = dir + "/MANIFEST";
+ mfValid = is_runfiles_manifest(mf);
+ if (!mfValid) {
+ mf = dir + "_manifest";
+ mfValid = is_runfiles_manifest(mf);
+ }
+ }
+
+ if (!dirValid &&
+ (ends_with(mf, ".runfiles_manifest") || ends_with(mf, "/MANIFEST"))) {
+ static const size_t kSubstrLen = 9; // "_manifest" or "/MANIFEST"
+ dir = mf.substr(0, mf.size() - kSubstrLen);
+ dirValid = is_runfiles_directory(dir);
+ }
+
+ if (mfValid) {
+ *out_manifest = mf;
+ }
+
+ if (dirValid) {
+ *out_directory = dir;
+ }
+
+ return true;
+}
+
+} // namespace
+
+} // namespace runfiles
+} // namespace cc
+} // namespace rules_cc
diff --git a/cc/runfiles/runfiles.h b/cc/runfiles/runfiles.h
new file mode 100644
index 0000000..d80a602
--- /dev/null
+++ b/cc/runfiles/runfiles.h
@@ -0,0 +1,264 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+// Runfiles lookup library for Bazel-built C++ binaries and tests.
+//
+// USAGE:
+// 1. Depend on this runfiles library from your build rule:
+//
+// cc_binary(
+// name = "my_binary",
+// ...
+// deps = ["@rules_cc//cc/runfiles"],
+// )
+//
+// 2. Include the runfiles library.
+//
+// #include "rules_cc/cc/runfiles/runfiles.h"
+//
+// using rules_cc::cc::runfiles::Runfiles;
+//
+// 3. Create a Runfiles object and use rlocation to look up runfile paths:
+//
+// int main(int argc, char** argv) {
+// std::string error;
+// std::unique_ptr<Runfiles> runfiles(
+// Runfiles::Create(argv[0], BAZEL_CURRENT_REPOSITORY, &error));
+//
+// // Important:
+// // If this is a test, use
+// // Runfiles::CreateForTest(BAZEL_CURRENT_REPOSITORY, &error).
+//
+// if (runfiles == nullptr) {
+// ... // error handling
+// }
+// std::string path =
+// runfiles->Rlocation("my_workspace/path/to/my/data.txt");
+// ...
+//
+// The code above creates a Runfiles object and retrieves a runfile path.
+// The BAZEL_CURRENT_REPOSITORY macro is available in every target that
+// depends on the runfiles library.
+//
+// The Runfiles::Create function uses the runfiles manifest and the
+// runfiles directory from the RUNFILES_MANIFEST_FILE and RUNFILES_DIR
+// environment variables. If not present, the function looks for the
+// manifest and directory near argv[0], the path of the main program.
+//
+// To start child processes that also need runfiles, you need to set the right
+// environment variables for them:
+//
+// std::unique_ptr<Runfiles> runfiles(Runfiles::Create(
+// argv[0], BAZEL_CURRENT_REPOSITORY, &error));
+//
+// std::string path = runfiles->Rlocation("path/to/binary"));
+// if (!path.empty()) {
+// ... // create "args" argument vector for execv
+// const auto envvars = runfiles->EnvVars();
+// pid_t child = fork();
+// if (child) {
+// int status;
+// waitpid(child, &status, 0);
+// } else {
+// for (const auto i : envvars) {
+// setenv(i.first.c_str(), i.second.c_str(), 1);
+// }
+// execv(args[0], args);
+// }
+
+#ifndef RULES_CC_CC_RUNFILES_RUNFILES_H_
+#define RULES_CC_CC_RUNFILES_RUNFILES_H_ 1
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace rules_cc {
+namespace cc {
+namespace runfiles {
+
+class Runfiles {
+ public:
+ virtual ~Runfiles() {}
+
+ // Returns a new `Runfiles` instance.
+ //
+ // Use this from within `cc_test` rules.
+ //
+ // Returns nullptr on error. If `error` is provided, the method prints an
+ // error message into it.
+ //
+ // This method looks at the RUNFILES_MANIFEST_FILE and TEST_SRCDIR
+ // environment variables.
+ //
+ // If source_repository is not provided, it defaults to the main repository
+ // (also known as the workspace).
+ static Runfiles* CreateForTest(std::string* error = nullptr);
+ static Runfiles* CreateForTest(const std::string& source_repository,
+ std::string* error = nullptr);
+
+ // Returns a new `Runfiles` instance.
+ //
+ // Use this from `cc_binary` or `cc_library` rules. You may pass an empty
+ // `argv0` if `argv[0]` from the `main` method is unknown.
+ //
+ // Returns nullptr on error. If `error` is provided, the method prints an
+ // error message into it.
+ //
+ // This method looks at the RUNFILES_MANIFEST_FILE and RUNFILES_DIR
+ // environment variables. If either is empty, the method looks for the
+ // manifest or directory using the other environment variable, or using argv0
+ // (unless it's empty).
+ //
+ // If source_repository is not provided, it defaults to the main repository
+ // (also known as the workspace).
+ static Runfiles* Create(const std::string& argv0,
+ std::string* error = nullptr);
+ static Runfiles* Create(const std::string& argv0,
+ const std::string& source_repository,
+ std::string* error = nullptr);
+
+ // Returns a new `Runfiles` instance.
+ //
+ // Use this from any `cc_*` rule if you want to manually specify the paths to
+ // the runfiles manifest and/or runfiles directory. You may pass an empty
+ // `argv0` if `argv[0]` from the `main` method is unknown.
+ //
+ // This method is the same as `Create(argv0, error)`, except it uses
+ // `runfiles_manifest_file` and `runfiles_dir` as the corresponding
+ // environment variable values, instead of looking up the actual environment
+ // variables.
+ static Runfiles* Create(const std::string& argv0,
+ const std::string& runfiles_manifest_file,
+ const std::string& runfiles_dir,
+ std::string* error = nullptr);
+ static Runfiles* Create(const std::string& argv0,
+ const std::string& runfiles_manifest_file,
+ const std::string& runfiles_dir,
+ const std::string& source_repository,
+ std::string* error = nullptr);
+
+ // Returns the runtime path of a runfile.
+ //
+ // Runfiles are data-dependencies of Bazel-built binaries and tests.
+ //
+ // The returned path may not exist. The caller should verify the path's
+ // existence.
+ //
+ // The function may return an empty string if it cannot find a runfile.
+ //
+ // Args:
+ // path: runfiles-root-relative path of the runfile; must not be empty and
+ // must not contain uplevel references.
+ // source_repository: if provided, overrides the source repository set when
+ // this Runfiles instance was created.
+ // Returns:
+ // the path to the runfile, which the caller should check for existence, or
+ // an empty string if the method doesn't know about this runfile
+ std::string Rlocation(const std::string& path) const;
+ std::string Rlocation(const std::string& path,
+ const std::string& source_repository) const;
+
+ // Returns environment variables for subprocesses.
+ //
+ // The caller should set the returned key-value pairs in the environment of
+ // subprocesses, so that those subprocesses can also access runfiles (in case
+ // they are also Bazel-built binaries).
+ const std::vector<std::pair<std::string, std::string> >& EnvVars() const {
+ return envvars_;
+ }
+
+ // Returns a new Runfiles instance that by default uses the provided source
+ // repository as a default for all calls to Rlocation.
+ //
+ // The current instance remains valid.
+ std::unique_ptr<Runfiles> WithSourceRepository(
+ const std::string& source_repository) const {
+ return std::unique_ptr<Runfiles>(new Runfiles(
+ runfiles_map_, directory_, repo_mapping_, envvars_, source_repository));
+ }
+
+ private:
+ Runfiles(
+ std::map<std::string, std::string> runfiles_map, std::string directory,
+ std::map<std::pair<std::string, std::string>, std::string> repo_mapping,
+ std::vector<std::pair<std::string, std::string> > envvars,
+ std::string source_repository)
+ : runfiles_map_(std::move(runfiles_map)),
+ directory_(std::move(directory)),
+ repo_mapping_(std::move(repo_mapping)),
+ envvars_(std::move(envvars)),
+ source_repository_(std::move(source_repository)) {}
+ Runfiles(const Runfiles&) = delete;
+ Runfiles(Runfiles&&) = delete;
+ Runfiles& operator=(const Runfiles&) = delete;
+ Runfiles& operator=(Runfiles&&) = delete;
+
+ static std::string RlocationUnchecked(
+ const std::string& path,
+ const std::map<std::string, std::string>& runfiles_map,
+ const std::string& directory);
+
+ const std::map<std::string, std::string> runfiles_map_;
+ const std::string directory_;
+ const std::map<std::pair<std::string, std::string>, std::string>
+ repo_mapping_;
+ const std::vector<std::pair<std::string, std::string> > envvars_;
+ const std::string source_repository_;
+};
+
+// The "testing" namespace contains functions that allow unit testing the code.
+// Do not use these outside of runfiles_test.cc, they are only part of the
+// public API for the benefit of the tests.
+// These functions and their interface may change without notice.
+namespace testing {
+
+// For testing only.
+//
+// Computes the path of the runfiles manifest and the runfiles directory.
+//
+// If the method finds both a valid manifest and valid directory according to
+// `is_runfiles_manifest` and `is_runfiles_directory`, then the method sets
+// the corresponding values to `out_manifest` and `out_directory` and returns
+// true.
+//
+// If the method only finds a valid manifest or a valid directory, but not
+// both, then it sets the corresponding output variable (`out_manifest` or
+// `out_directory`) to the value while clearing the other output variable. The
+// method still returns true in this case.
+//
+// If the method cannot find either a valid manifest or valid directory, it
+// clears both output variables and returns false.
+bool TestOnly_PathsFrom(
+ const std::string& argv0, std::string runfiles_manifest_file,
+ std::string runfiles_dir,
+ std::function<bool(const std::string&)> is_runfiles_manifest,
+ std::function<bool(const std::string&)> is_runfiles_directory,
+ std::string* out_manifest, std::string* out_directory);
+
+// For testing only.
+// Returns true if `path` is an absolute Unix or Windows path.
+// For Windows paths, this function does not regard drive-less absolute paths
+// (i.e. absolute-on-current-drive, e.g. "\foo\bar") as absolute and returns
+// false for these.
+bool TestOnly_IsAbsolute(const std::string& path);
+
+} // namespace testing
+} // namespace runfiles
+} // namespace cc
+} // namespace rules_cc
+
+#endif // RULES_CC_CC_RUNFILES_RUNFILES_H_
diff --git a/tests/runfiles/BUILD b/tests/runfiles/BUILD
new file mode 100644
index 0000000..4d906a7
--- /dev/null
+++ b/tests/runfiles/BUILD
@@ -0,0 +1,12 @@
+load("//cc:cc_test.bzl", "cc_test")
+
+licenses(["notice"])
+
+cc_test(
+ name = "runfiles_test",
+ srcs = ["runfiles_test.cc"],
+ deps = [
+ "//cc/runfiles",
+ "@googletest//:gtest_main",
+ ],
+)
diff --git a/tests/runfiles/runfiles_test.cc b/tests/runfiles/runfiles_test.cc
new file mode 100644
index 0000000..0f7e894
--- /dev/null
+++ b/tests/runfiles/runfiles_test.cc
@@ -0,0 +1,880 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+#include "rules_cc/cc/runfiles/runfiles.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#endif // _WIN32
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#define RUNFILES_TEST_TOSTRING_HELPER(x) #x
+#define RUNFILES_TEST_TOSTRING(x) RUNFILES_TEST_TOSTRING_HELPER(x)
+#define LINE_AS_STRING() RUNFILES_TEST_TOSTRING(__LINE__)
+
+namespace rules_cc {
+namespace cc {
+namespace runfiles {
+namespace {
+
+using rules_cc::cc::runfiles::testing::TestOnly_IsAbsolute;
+using rules_cc::cc::runfiles::testing::TestOnly_PathsFrom;
+using std::cerr;
+using std::endl;
+using std::function;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+class RunfilesTest : public ::testing::Test {
+ protected:
+ // Create a temporary file that is deleted with the destructor.
+ class MockFile {
+ public:
+ // Create an empty file with the given name under $TEST_TMPDIR.
+ static MockFile* Create(const string& name);
+
+ // Create a file with the given name and contents under $TEST_TMPDIR.
+ // The method ensures to create all parent directories, so `name` is allowed
+ // to contain directory components.
+ static MockFile* Create(const string& name, const vector<string>& lines);
+
+ ~MockFile();
+ const string& Path() const { return path_; }
+
+ string DirName() const {
+ string::size_type pos = path_.find_last_of('/');
+ return pos == string::npos ? "" : path_.substr(0, pos);
+ }
+
+ private:
+ MockFile(const string& path) : path_(path) {}
+ MockFile(const MockFile&) = delete;
+ MockFile(MockFile&&) = delete;
+ MockFile& operator=(const MockFile&) = delete;
+ MockFile& operator=(MockFile&&) = delete;
+
+ const string path_;
+ };
+
+ void AssertEnvvars(const Runfiles& runfiles,
+ const string& expected_manifest_file,
+ const string& expected_directory);
+
+ static string GetTemp();
+};
+
+void RunfilesTest::AssertEnvvars(const Runfiles& runfiles,
+ const string& expected_manifest_file,
+ const string& expected_directory) {
+ vector<pair<string, string> > expected = {
+ {"RUNFILES_MANIFEST_FILE", expected_manifest_file},
+ {"RUNFILES_DIR", expected_directory},
+ {"JAVA_RUNFILES", expected_directory}};
+ ASSERT_EQ(runfiles.EnvVars(), expected);
+}
+
+string RunfilesTest::GetTemp() {
+#ifdef _WIN32
+ DWORD size = ::GetEnvironmentVariableA("TEST_TMPDIR", nullptr, 0);
+ if (size == 0) {
+ return string(); // unset or empty envvar
+ }
+ unique_ptr<char[]> value(new char[size]);
+ ::GetEnvironmentVariableA("TEST_TMPDIR", value.get(), size);
+ return value.get();
+#else
+ char* result = getenv("TEST_TMPDIR");
+ return result != nullptr ? string(result) : string();
+#endif
+}
+
+RunfilesTest::MockFile* RunfilesTest::MockFile::Create(const string& name) {
+ return Create(name, vector<string>());
+}
+
+RunfilesTest::MockFile* RunfilesTest::MockFile::Create(
+ const string& name, const vector<string>& lines) {
+ if (name.find("..") != string::npos || TestOnly_IsAbsolute(name)) {
+ cerr << "WARNING: " << __FILE__ << "(" << __LINE__ << "): bad name: \""
+ << name << "\"" << endl;
+ return nullptr;
+ }
+
+ string tmp(RunfilesTest::GetTemp());
+ if (tmp.empty()) {
+ cerr << "WARNING: " << __FILE__ << "(" << __LINE__
+ << "): $TEST_TMPDIR is empty" << endl;
+ return nullptr;
+ }
+ string path(tmp + "/" + name);
+
+ string::size_type i = 0;
+#ifdef _WIN32
+ while ((i = name.find_first_of("/\\", i + 1)) != string::npos) {
+ string d = tmp + "\\" + name.substr(0, i);
+ if (!CreateDirectoryA(d.c_str(), nullptr)) {
+ cerr << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): failed to create directory \"" << d << "\"" << endl;
+ return nullptr;
+ }
+ }
+#else
+ while ((i = name.find_first_of('/', i + 1)) != string::npos) {
+ string d = tmp + "/" + name.substr(0, i);
+ if (mkdir(d.c_str(), 0777)) {
+ cerr << "ERROR: " << __FILE__ << "(" << __LINE__
+ << "): failed to create directory \"" << d << "\"" << endl;
+ return nullptr;
+ }
+ }
+#endif
+
+ auto stm = std::ofstream(path);
+ for (auto i : lines) {
+ stm << i << std::endl;
+ }
+ return new MockFile(path);
+}
+
+RunfilesTest::MockFile::~MockFile() { std::remove(path_.c_str()); }
+
+TEST_F(RunfilesTest, CreatesManifestBasedRunfilesFromManifestNextToBinary) {
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" LINE_AS_STRING() ".runfiles_manifest", {"a/b c/d"}));
+ ASSERT_TRUE(mf != nullptr);
+ string argv0(mf->Path().substr(
+ 0, mf->Path().size() - string(".runfiles_manifest").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+ // We know it's manifest-based because it returns empty string for unknown
+ // paths.
+ EXPECT_EQ(r->Rlocation("unknown"), "");
+ AssertEnvvars(*r, mf->Path(), "");
+}
+
+TEST_F(RunfilesTest,
+ CreatesManifestBasedRunfilesFromManifestInRunfilesDirectory) {
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" LINE_AS_STRING() ".runfiles/MANIFEST", {"a/b c/d"}));
+ ASSERT_TRUE(mf != nullptr);
+ string argv0(mf->Path().substr(
+ 0, mf->Path().size() - string(".runfiles/MANIFEST").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+ EXPECT_EQ(r->Rlocation("foo"), argv0 + ".runfiles/foo");
+ AssertEnvvars(*r, mf->Path(), argv0 + ".runfiles");
+}
+
+TEST_F(RunfilesTest, CreatesManifestBasedRunfilesFromEnvvar) {
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" LINE_AS_STRING() ".runfiles_manifest", {"a/b c/d"}));
+ ASSERT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create("ignore-argv0", mf->Path(),
+ "non-existent-runfiles_dir", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+ // We know it's manifest-based because it returns empty string for unknown
+ // paths.
+ EXPECT_EQ(r->Rlocation("unknown"), "");
+ AssertEnvvars(*r, mf->Path(), "");
+}
+
+TEST_F(RunfilesTest, CannotCreateManifestBasedRunfilesDueToBadManifest) {
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" LINE_AS_STRING() ".runfiles_manifest", {"a b", "nospace"}));
+ ASSERT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(
+ Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
+ EXPECT_EQ(r, nullptr);
+ EXPECT_NE(error.find("bad runfiles manifest entry"), string::npos);
+ EXPECT_NE(error.find("line #2: \"nospace\""), string::npos);
+}
+
+TEST_F(RunfilesTest, ManifestBasedRunfilesRlocationAndEnvVars) {
+ unique_ptr<MockFile> mf(
+ MockFile::Create("foo" LINE_AS_STRING() ".runfiles_manifest",
+ {
+ "a/b c/d",
+ "e/f target path with spaces",
+ " h/\\si j k",
+ " dir\\swith\\sspaces l/m",
+ " h/\\n\\s\\bi j k \\n\\b",
+ "not_escaped with\\backslash and spaces",
+ }));
+ ASSERT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(
+ Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
+
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+ EXPECT_EQ(r->Rlocation("c/d"), "");
+ EXPECT_EQ(r->Rlocation(""), "");
+ EXPECT_EQ(r->Rlocation("foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/"), "");
+ EXPECT_EQ(r->Rlocation("foo/bar"), "");
+ EXPECT_EQ(r->Rlocation("../foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/.."), "");
+ EXPECT_EQ(r->Rlocation("foo/../bar"), "");
+ EXPECT_EQ(r->Rlocation("./foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/."), "");
+ EXPECT_EQ(r->Rlocation("foo/./bar"), "");
+ EXPECT_EQ(r->Rlocation("//foo"), "");
+ EXPECT_EQ(r->Rlocation("foo//"), "");
+ EXPECT_EQ(r->Rlocation("foo//bar"), "");
+ EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+ EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+ EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+ EXPECT_EQ(r->Rlocation("a/b/file"), "c/d/file");
+ EXPECT_EQ(r->Rlocation("a/b/deeply/nested/file"), "c/d/deeply/nested/file");
+ EXPECT_EQ(r->Rlocation("a/b/deeply/nested/file with spaces"),
+ "c/d/deeply/nested/file with spaces");
+ EXPECT_EQ(r->Rlocation("e/f"), "target path with spaces");
+ EXPECT_EQ(r->Rlocation("e/f/file"), "target path with spaces/file");
+ EXPECT_EQ(r->Rlocation("h/ i"), "j k");
+ EXPECT_EQ(r->Rlocation("h/\n \\i"), "j k \n\\");
+ EXPECT_EQ(r->Rlocation("dir with spaces"), "l/m");
+ EXPECT_EQ(r->Rlocation("dir with spaces/file"), "l/m/file");
+ EXPECT_EQ(r->Rlocation("not_escaped"), "with\\backslash and spaces");
+}
+
+TEST_F(RunfilesTest, DirectoryBasedRunfilesRlocationAndEnvVars) {
+ unique_ptr<MockFile> dummy(
+ MockFile::Create("foo" LINE_AS_STRING() ".runfiles/dummy", {"a/b c/d"}));
+ ASSERT_TRUE(dummy != nullptr);
+ string dir = dummy->DirName();
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create("ignore-argv0", "", dir, &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("a/b"), dir + "/a/b");
+ EXPECT_EQ(r->Rlocation("c/d"), dir + "/c/d");
+ EXPECT_EQ(r->Rlocation(""), "");
+ EXPECT_EQ(r->Rlocation("foo"), dir + "/foo");
+ EXPECT_EQ(r->Rlocation("foo/"), dir + "/foo/");
+ EXPECT_EQ(r->Rlocation("foo/bar"), dir + "/foo/bar");
+ EXPECT_EQ(r->Rlocation("../foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/.."), "");
+ EXPECT_EQ(r->Rlocation("foo/../bar"), "");
+ EXPECT_EQ(r->Rlocation("./foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/."), "");
+ EXPECT_EQ(r->Rlocation("foo/./bar"), "");
+ EXPECT_EQ(r->Rlocation("//foo"), "");
+ EXPECT_EQ(r->Rlocation("foo//"), "");
+ EXPECT_EQ(r->Rlocation("foo//bar"), "");
+ EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+ EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+ EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+ AssertEnvvars(*r, "", dir);
+}
+
+TEST_F(RunfilesTest, ManifestAndDirectoryBasedRunfilesRlocationAndEnvVars) {
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" LINE_AS_STRING() ".runfiles/MANIFEST", {"a/b c/d"}));
+ ASSERT_TRUE(mf != nullptr);
+ string dir = mf->DirName();
+
+ string error;
+ unique_ptr<Runfiles> r(
+ Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
+
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+ EXPECT_EQ(r->Rlocation("c/d"), dir + "/c/d");
+ EXPECT_EQ(r->Rlocation(""), "");
+ EXPECT_EQ(r->Rlocation("foo"), dir + "/foo");
+ EXPECT_EQ(r->Rlocation("foo/"), dir + "/foo/");
+ EXPECT_EQ(r->Rlocation("foo/bar"), dir + "/foo/bar");
+ EXPECT_EQ(r->Rlocation("../foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/.."), "");
+ EXPECT_EQ(r->Rlocation("foo/../bar"), "");
+ EXPECT_EQ(r->Rlocation("./foo"), "");
+ EXPECT_EQ(r->Rlocation("foo/."), "");
+ EXPECT_EQ(r->Rlocation("foo/./bar"), "");
+ EXPECT_EQ(r->Rlocation("//foo"), "");
+ EXPECT_EQ(r->Rlocation("foo//"), "");
+ EXPECT_EQ(r->Rlocation("foo//bar"), "");
+ EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+ EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+ EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+ EXPECT_EQ(r->Rlocation("a/b/file"), "c/d/file");
+ EXPECT_EQ(r->Rlocation("a/b/deeply/nested/file"), "c/d/deeply/nested/file");
+ AssertEnvvars(*r, mf->Path(), dir);
+}
+
+TEST_F(RunfilesTest, ManifestBasedRunfilesEnvVars) {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles_manifest")));
+ ASSERT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(
+ Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ AssertEnvvars(*r, mf->Path(), "");
+}
+
+TEST_F(RunfilesTest, CreatesDirectoryBasedRunfilesFromDirectoryNextToBinary) {
+ // We create a directory as a side-effect of creating a mock file.
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/dummy")));
+ string argv0(mf->Path().substr(
+ 0, mf->Path().size() - string(".runfiles/dummy").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("a/b"), argv0 + ".runfiles/a/b");
+ // We know it's directory-based because it returns some result for unknown
+ // paths.
+ EXPECT_EQ(r->Rlocation("unknown"), argv0 + ".runfiles/unknown");
+ AssertEnvvars(*r, "", argv0 + ".runfiles");
+}
+
+TEST_F(RunfilesTest, CreatesDirectoryBasedRunfilesFromEnvvar) {
+ // We create a directory as a side-effect of creating a mock file.
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/dummy")));
+ string dir = mf->DirName();
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create("ignore-argv0", "", dir, &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("a/b"), dir + "/a/b");
+ EXPECT_EQ(r->Rlocation("foo"), dir + "/foo");
+ EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+ EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+ EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+ AssertEnvvars(*r, "", dir);
+}
+
+TEST_F(RunfilesTest, FailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined) {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/MANIFEST")));
+ ASSERT_TRUE(mf != nullptr);
+
+ string error;
+ unique_ptr<Runfiles> r(
+ Runfiles::Create("ignore-argv0", mf->Path(), "whatever", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ // We create a directory as a side-effect of creating a mock file.
+ mf.reset(MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/dummy")));
+ r.reset(Runfiles::Create("ignore-argv0", "", mf->DirName(), &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ r.reset(Runfiles::Create("ignore-argv0", /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"", &error));
+ EXPECT_EQ(r, nullptr);
+ EXPECT_NE(error.find("cannot find runfiles"), string::npos);
+}
+
+TEST_F(RunfilesTest, MockFileTest) {
+ {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() "/..")));
+ EXPECT_TRUE(mf == nullptr);
+ }
+
+ {
+ unique_ptr<MockFile> mf(MockFile::Create(string("/Foo" LINE_AS_STRING())));
+ EXPECT_TRUE(mf == nullptr);
+ }
+
+ {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("C:/Foo" LINE_AS_STRING())));
+ EXPECT_TRUE(mf == nullptr);
+ }
+
+ string path;
+ {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() "/bar1/qux")));
+ ASSERT_TRUE(mf != nullptr);
+ path = mf->Path();
+
+ std::ifstream stm(path);
+ EXPECT_TRUE(stm.good());
+ string actual;
+ stm >> actual;
+ EXPECT_TRUE(actual.empty());
+ }
+ {
+ std::ifstream stm(path);
+ EXPECT_FALSE(stm.good());
+ }
+
+ {
+ unique_ptr<MockFile> mf(MockFile::Create(
+ string("foo" LINE_AS_STRING() "/bar2/qux"), vector<string>()));
+ ASSERT_TRUE(mf != nullptr);
+ path = mf->Path();
+
+ std::ifstream stm(path);
+ EXPECT_TRUE(stm.good());
+ string actual;
+ stm >> actual;
+ EXPECT_TRUE(actual.empty());
+ }
+ {
+ std::ifstream stm(path);
+ EXPECT_FALSE(stm.good());
+ }
+
+ {
+ unique_ptr<MockFile> mf(
+ MockFile::Create(string("foo" LINE_AS_STRING() "/bar3/qux"),
+ {"hello world", "you are beautiful"}));
+ ASSERT_TRUE(mf != nullptr);
+ path = mf->Path();
+
+ std::ifstream stm(path);
+ EXPECT_TRUE(stm.good());
+ string actual;
+ std::getline(stm, actual);
+ EXPECT_EQ("hello world", actual);
+ std::getline(stm, actual);
+ EXPECT_EQ("you are beautiful", actual);
+ std::getline(stm, actual);
+ EXPECT_EQ("", actual);
+ }
+ {
+ std::ifstream stm(path);
+ EXPECT_FALSE(stm.good());
+ }
+}
+
+TEST_F(RunfilesTest, IsAbsolute) {
+ EXPECT_FALSE(TestOnly_IsAbsolute("foo"));
+ EXPECT_FALSE(TestOnly_IsAbsolute("foo/bar"));
+ EXPECT_FALSE(TestOnly_IsAbsolute("\\foo"));
+ EXPECT_TRUE(TestOnly_IsAbsolute("c:\\foo"));
+ EXPECT_TRUE(TestOnly_IsAbsolute("c:/foo"));
+ EXPECT_TRUE(TestOnly_IsAbsolute("/foo"));
+ EXPECT_TRUE(TestOnly_IsAbsolute("x:\\foo"));
+ EXPECT_FALSE(TestOnly_IsAbsolute("::\\foo"));
+ EXPECT_FALSE(TestOnly_IsAbsolute("x\\foo"));
+ EXPECT_FALSE(TestOnly_IsAbsolute("x:"));
+ EXPECT_TRUE(TestOnly_IsAbsolute("x:\\"));
+}
+
+TEST_F(RunfilesTest, PathsFromEnvVars) {
+ string mf, dir, rm;
+
+ // Both envvars have a valid value.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1.runfiles/MANIFEST", "mock2.runfiles",
+ [](const string& path) { return path == "mock1.runfiles/MANIFEST"; },
+ [](const string& path) { return path == "mock2.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "mock1.runfiles/MANIFEST");
+ EXPECT_EQ(dir, "mock2.runfiles");
+
+ // RUNFILES_MANIFEST_FILE is invalid but RUNFILES_DIR is good and there's a
+ // runfiles manifest in the runfiles directory.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1.runfiles/MANIFEST", "mock2.runfiles",
+ [](const string& path) { return path == "mock2.runfiles/MANIFEST"; },
+ [](const string& path) { return path == "mock2.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "mock2.runfiles/MANIFEST");
+ EXPECT_EQ(dir, "mock2.runfiles");
+
+ // RUNFILES_MANIFEST_FILE is invalid but RUNFILES_DIR is good, but there's no
+ // runfiles manifest in the runfiles directory.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1.runfiles/MANIFEST", "mock2.runfiles",
+ [](const string& path) { return false; },
+ [](const string& path) { return path == "mock2.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "");
+ EXPECT_EQ(dir, "mock2.runfiles");
+
+ // RUNFILES_DIR is invalid but RUNFILES_MANIFEST_FILE is good, and it is in
+ // a valid-looking runfiles directory.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1.runfiles/MANIFEST", "mock2",
+ [](const string& path) { return path == "mock1.runfiles/MANIFEST"; },
+ [](const string& path) { return path == "mock1.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "mock1.runfiles/MANIFEST");
+ EXPECT_EQ(dir, "mock1.runfiles");
+
+ // RUNFILES_DIR is invalid but RUNFILES_MANIFEST_FILE is good, but it is not
+ // in any valid-looking runfiles directory.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1/MANIFEST", "mock2",
+ [](const string& path) { return path == "mock1/MANIFEST"; },
+ [](const string& path) { return false; }, &mf, &dir));
+ EXPECT_EQ(mf, "mock1/MANIFEST");
+ EXPECT_EQ(dir, "");
+
+ // Both envvars are invalid, but there's a manifest in a runfiles directory
+ // next to argv0, however there's no other content in the runfiles directory.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1/MANIFEST", "mock2",
+ [](const string& path) { return path == "argv0.runfiles/MANIFEST"; },
+ [](const string& path) { return false; }, &mf, &dir));
+ EXPECT_EQ(mf, "argv0.runfiles/MANIFEST");
+ EXPECT_EQ(dir, "");
+
+ // Both envvars are invalid, but there's a manifest next to argv0. There's
+ // no runfiles tree anywhere.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1/MANIFEST", "mock2",
+ [](const string& path) { return path == "argv0.runfiles_manifest"; },
+ [](const string& path) { return false; }, &mf, &dir));
+ EXPECT_EQ(mf, "argv0.runfiles_manifest");
+ EXPECT_EQ(dir, "");
+
+ // Both envvars are invalid, but there's a valid manifest next to argv0, and a
+ // valid runfiles directory (without a manifest in it).
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1/MANIFEST", "mock2",
+ [](const string& path) { return path == "argv0.runfiles_manifest"; },
+ [](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "argv0.runfiles_manifest");
+ EXPECT_EQ(dir, "argv0.runfiles");
+
+ // Both envvars are invalid, but there's a valid runfiles directory next to
+ // argv0, though no manifest in it.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1/MANIFEST", "mock2",
+ [](const string& path) { return false; },
+ [](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "");
+ EXPECT_EQ(dir, "argv0.runfiles");
+
+ // Both envvars are invalid, but there's a valid runfiles directory next to
+ // argv0 with a valid manifest in it.
+ EXPECT_TRUE(TestOnly_PathsFrom(
+ "argv0", "mock1/MANIFEST", "mock2",
+ [](const string& path) { return path == "argv0.runfiles/MANIFEST"; },
+ [](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir));
+ EXPECT_EQ(mf, "argv0.runfiles/MANIFEST");
+ EXPECT_EQ(dir, "argv0.runfiles");
+}
+
+TEST_F(RunfilesTest, ManifestBasedRlocationWithRepoMapping_fromMain) {
+ string uid = LINE_AS_STRING();
+ unique_ptr<MockFile> rm(
+ MockFile::Create("foo" + uid + ".repo_mapping",
+ {",config.json,config.json+1.2.3", ",my_module,_main",
+ ",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
+ "protobuf+3.19.2,config.json,config.json+1.2.3",
+ "protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
+ ASSERT_TRUE(rm != nullptr);
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" + uid + ".runfiles_manifest",
+ {"_repo_mapping " + rm->Path(), "config.json /etc/config.json",
+ "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile",
+ "_main/bar/runfile /the/path/./to/other//other runfile.txt",
+ "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory"}));
+ ASSERT_TRUE(mf != nullptr);
+ string argv0(mf->Path().substr(
+ 0, mf->Path().size() - string(".runfiles_manifest").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"",
+ /*source_repository=*/"", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("my_module/bar/runfile"),
+ "/the/path/./to/other//other runfile.txt");
+ EXPECT_EQ(r->Rlocation("my_workspace/bar/runfile"),
+ "/the/path/./to/other//other runfile.txt");
+ EXPECT_EQ(r->Rlocation("my_protobuf/foo/runfile"),
+ "C:/Actual Path\\protobuf\\runfile");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir"), "E:\\Actual Path\\Directory");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/file"),
+ "E:\\Actual Path\\Directory/file");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"), "");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), "");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"), "");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le"), "");
+
+ EXPECT_EQ(r->Rlocation("_main/bar/runfile"),
+ "/the/path/./to/other//other runfile.txt");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
+ "C:/Actual Path\\protobuf\\runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
+ "E:\\Actual Path\\Directory");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
+ "E:\\Actual Path\\Directory/file");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("config.json"), "/etc/config.json");
+ EXPECT_EQ(r->Rlocation("_main"), "");
+ EXPECT_EQ(r->Rlocation("my_module"), "");
+ EXPECT_EQ(r->Rlocation("protobuf"), "");
+}
+
+TEST_F(RunfilesTest, ManifestBasedRlocationWithRepoMapping_fromOtherRepo) {
+ string uid = LINE_AS_STRING();
+ unique_ptr<MockFile> rm(
+ MockFile::Create("foo" + uid + ".repo_mapping",
+ {",config.json,config.json+1.2.3", ",my_module,_main",
+ ",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
+ "protobuf+3.19.2,config.json,config.json+1.2.3",
+ "protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
+ ASSERT_TRUE(rm != nullptr);
+ unique_ptr<MockFile> mf(MockFile::Create(
+ "foo" + uid + ".runfiles_manifest",
+ {"_repo_mapping " + rm->Path(), "config.json /etc/config.json",
+ "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile",
+ "_main/bar/runfile /the/path/./to/other//other runfile.txt",
+ "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory"}));
+ ASSERT_TRUE(mf != nullptr);
+ string argv0(mf->Path().substr(
+ 0, mf->Path().size() - string(".runfiles_manifest").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"",
+ "protobuf+3.19.2", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
+ "C:/Actual Path\\protobuf\\runfile");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), "E:\\Actual Path\\Directory");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"),
+ "E:\\Actual Path\\Directory/file");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/de eply/nes ted/fi+le"),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("my_module/bar/runfile"), "");
+ EXPECT_EQ(r->Rlocation("my_protobuf/foo/runfile"), "");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir"), "");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/file"), "");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"), "");
+
+ EXPECT_EQ(r->Rlocation("_main/bar/runfile"),
+ "/the/path/./to/other//other runfile.txt");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
+ "C:/Actual Path\\protobuf\\runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
+ "E:\\Actual Path\\Directory");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
+ "E:\\Actual Path\\Directory/file");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("config.json"), "/etc/config.json");
+ EXPECT_EQ(r->Rlocation("_main"), "");
+ EXPECT_EQ(r->Rlocation("my_module"), "");
+ EXPECT_EQ(r->Rlocation("protobuf"), "");
+}
+
+TEST_F(RunfilesTest, DirectoryBasedRlocationWithRepoMapping_fromMain) {
+ string uid = LINE_AS_STRING();
+ unique_ptr<MockFile> rm(
+ MockFile::Create("foo" + uid + ".runfiles/_repo_mapping",
+ {",config.json,config.json+1.2.3", ",my_module,_main",
+ ",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
+ "protobuf+3.19.2,config.json,config.json+1.2.3",
+ "protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
+ ASSERT_TRUE(rm != nullptr);
+ string dir = rm->DirName();
+ string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"",
+ /*source_repository=*/"", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("my_module/bar/runfile"), dir + "/_main/bar/runfile");
+ EXPECT_EQ(r->Rlocation("my_workspace/bar/runfile"),
+ dir + "/_main/bar/runfile");
+ EXPECT_EQ(r->Rlocation("my_protobuf/foo/runfile"),
+ dir + "/protobuf+3.19.2/foo/runfile");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir"),
+ dir + "/protobuf+3.19.2/bar/dir");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/file"),
+ dir + "/protobuf+3.19.2/bar/dir/file");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
+ dir + "/protobuf/foo/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("_main/bar/runfile"), dir + "/_main/bar/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
+ dir + "/protobuf+3.19.2/foo/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
+ dir + "/protobuf+3.19.2/bar/dir");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
+ dir + "/protobuf+3.19.2/bar/dir/file");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("config.json"), dir + "/config.json");
+}
+
+TEST_F(RunfilesTest, DirectoryBasedRlocationWithRepoMapping_fromOtherRepo) {
+ string uid = LINE_AS_STRING();
+ unique_ptr<MockFile> rm(
+ MockFile::Create("foo" + uid + ".runfiles/_repo_mapping",
+ {",config.json,config.json+1.2.3", ",my_module,_main",
+ ",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
+ "protobuf+3.19.2,config.json,config.json+1.2.3",
+ "protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
+ ASSERT_TRUE(rm != nullptr);
+ string dir = rm->DirName();
+ string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"",
+ "protobuf+3.19.2", &error));
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
+ dir + "/protobuf+3.19.2/foo/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), dir + "/protobuf+3.19.2/bar/dir");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"),
+ dir + "/protobuf+3.19.2/bar/dir/file");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("my_module/bar/runfile"),
+ dir + "/my_module/bar/runfile");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/my_protobuf/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("_main/bar/runfile"), dir + "/_main/bar/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
+ dir + "/protobuf+3.19.2/foo/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
+ dir + "/protobuf+3.19.2/bar/dir");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
+ dir + "/protobuf+3.19.2/bar/dir/file");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("config.json"), dir + "/config.json");
+}
+
+TEST_F(RunfilesTest,
+ DirectoryBasedRlocationWithRepoMapping_fromOtherRepo_withSourceRepo) {
+ string uid = LINE_AS_STRING();
+ unique_ptr<MockFile> rm(
+ MockFile::Create("foo" + uid + ".runfiles/_repo_mapping",
+ {",config.json,config.json+1.2.3", ",my_module,_main",
+ ",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
+ "protobuf+3.19.2,config.json,config.json+1.2.3",
+ "protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
+ ASSERT_TRUE(rm != nullptr);
+ string dir = rm->DirName();
+ string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"",
+ /*source_repository=*/"", &error));
+ r = r->WithSourceRepository("protobuf+3.19.2");
+ ASSERT_TRUE(r != nullptr);
+ EXPECT_TRUE(error.empty());
+
+ EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
+ dir + "/protobuf+3.19.2/foo/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), dir + "/protobuf+3.19.2/bar/dir");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"),
+ dir + "/protobuf+3.19.2/bar/dir/file");
+ EXPECT_EQ(r->Rlocation("protobuf/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("my_module/bar/runfile"),
+ dir + "/my_module/bar/runfile");
+ EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/my_protobuf/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("_main/bar/runfile"), dir + "/_main/bar/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
+ dir + "/protobuf+3.19.2/foo/runfile");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
+ dir + "/protobuf+3.19.2/bar/dir");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
+ dir + "/protobuf+3.19.2/bar/dir/file");
+ EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
+ dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
+
+ EXPECT_EQ(r->Rlocation("config.json"), dir + "/config.json");
+}
+
+TEST_F(RunfilesTest, InvalidRepoMapping) {
+ string uid = LINE_AS_STRING();
+ unique_ptr<MockFile> rm(
+ MockFile::Create("foo" + uid + ".runfiles/_repo_mapping", {"a,b"}));
+ ASSERT_TRUE(rm != nullptr);
+ string dir = rm->DirName();
+ string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
+
+ string error;
+ unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
+ /*runfiles_dir=*/"",
+ /*source_repository=*/"", &error));
+ EXPECT_EQ(r, nullptr);
+ EXPECT_TRUE(error.find("bad repository mapping") != string::npos);
+}
+
+} // namespace
+} // namespace runfiles
+} // namespace cc
+} // namespace rules_cc