Bazel client: mock out read/write calls
Make blaze::ReadFileDescriptor(int fd, ...) and
blaze::WriteFile(int fd, ...) platform-independent
by mocking out the read(2) and write(2) calls.
Also rename ReadFileDescriptor to ReadFrom and
introduce a new WriteTo method that encapsulates
WriteFile's prior logic.
In particular these functions now take a
read_func/write_func function argument instead of
a file descriptor, so the read(2)/write(2) calls
can be mocked out.
This allows us to use these functions on Windows
too, where read(2)/write(2) are not implemented,
and we can inject a different
read_func/write_func.
See https://github.com/bazelbuild/bazel/issues/2107
--
MOS_MIGRATED_REVID=140195973
diff --git a/src/main/cpp/blaze_util.cc b/src/main/cpp/blaze_util.cc
index cfa2b92..0ab9b76 100644
--- a/src/main/cpp/blaze_util.cc
+++ b/src/main/cpp/blaze_util.cc
@@ -77,14 +77,15 @@
return cwd + separator + path;
}
-bool ReadFileDescriptor(int fd, string *content, int max_size) {
+bool ReadFrom(const std::function<int(void *, int)> &read_func, string *content,
+ int max_size) {
content->clear();
char buf[4096];
// OPT: This loop generates one spurious read on regular files.
- while (int r = read(fd, buf,
- max_size > 0
- ? std::min(max_size, static_cast<int>(sizeof buf))
- : sizeof buf)) {
+ while (int r = read_func(
+ buf, max_size > 0
+ ? std::min(max_size, static_cast<int>(sizeof buf))
+ : sizeof buf)) {
if (r == -1) {
if (errno == EINTR || errno == EAGAIN) continue;
return false;
@@ -98,40 +99,49 @@
}
}
}
- close(fd);
return true;
}
bool ReadFile(const string &filename, string *content, int max_size) {
int fd = open(filename.c_str(), O_RDONLY);
if (fd == -1) return false;
- return ReadFileDescriptor(fd, content, max_size);
+ bool result =
+ ReadFrom([fd](void *buf, int len) { return read(fd, buf, len); }, content,
+ max_size);
+ close(fd);
+ return result;
}
-// Writes 'content' into file 'filename', and makes it executable.
-// Returns false on failure, sets errno.
-bool WriteFile(const string &content, const string &filename) {
- return WriteFile(content.data(), content.size(), filename);
-}
-
-bool WriteFile(const void *data, size_t size, const std::string &filename) {
+bool WriteFile(const void* data, size_t size, const string &filename) {
UnlinkPath(filename); // We don't care about the success of this.
int fd = open(filename.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0755); // chmod +x
if (fd == -1) {
return false;
}
- int r = write(fd, data, size);
- if (r == -1) {
- return false;
- }
+ bool result = WriteTo(
+ [fd](const void *buf, size_t bufsize) { return write(fd, buf, bufsize); },
+ data, size);
int saved_errno = errno;
if (close(fd)) {
return false; // Can fail on NFS.
}
errno = saved_errno; // Caller should see errno from write().
+ return result;
+}
+
+bool WriteTo(const std::function<int(const void *, size_t)> &write_func,
+ const void *data, size_t size) {
+ int r = write_func(data, size);
+ if (r == -1) {
+ return false;
+ }
return static_cast<uint>(r) == size;
}
+bool WriteFile(const std::string &content, const std::string &filename) {
+ return WriteFile(content.c_str(), content.size(), filename);
+}
+
bool UnlinkPath(const string &file_path) {
return unlink(file_path.c_str()) == 0;
}
diff --git a/src/main/cpp/blaze_util.h b/src/main/cpp/blaze_util.h
index 06c75ed..cdef849 100644
--- a/src/main/cpp/blaze_util.h
+++ b/src/main/cpp/blaze_util.h
@@ -21,6 +21,7 @@
#include <sys/types.h>
+#include <functional>
#include <sstream>
#include <string>
#include <vector>
@@ -52,11 +53,12 @@
bool ReadFile(const std::string &filename, std::string *content,
int max_size = 0);
-// Replaces 'content' with contents of file descriptor 'fd'.
-// If `max_size` is positive, the method reads at most that many bytes; if it
-// otherwise the method reads the whole file.
+// Replaces 'content' with data read from a source using `read_func`.
+// If `max_size` is positive, the method reads at most that many bytes;
+// otherwise the method reads everything.
// Returns false on error. Can be called from a signal handler.
-bool ReadFileDescriptor(int fd, std::string *content, int max_size = 0);
+bool ReadFrom(const std::function<int(void *, int)> &read_func,
+ std::string *content, int max_size = 0);
// Writes 'content' into file 'filename', and makes it executable.
// Returns false on failure, sets errno.
@@ -66,6 +68,11 @@
// Returns false on failure, sets errno.
bool WriteFile(const void* data, size_t size, const std::string &filename);
+// Writes `size` bytes from `data` into a destination using `write_func`.
+// Returns false on failure, sets errno.
+bool WriteTo(const std::function<int(const void *, size_t)> &write_func,
+ const void *data, size_t size);
+
// Unlinks the file given by 'file_path'.
// Returns true on success. In case of failure sets errno.
bool UnlinkPath(const std::string &file_path);
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index ed0d9b3..5fdf3dd 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -261,23 +261,30 @@
if (pipe(fds)) {
pdie(blaze_exit_code::INTERNAL_ERROR, "pipe creation failed");
}
+ int recv_socket = fds[0];
+ int send_socket = fds[1];
int child = fork();
if (child == -1) {
pdie(blaze_exit_code::INTERNAL_ERROR, "fork() failed");
} else if (child > 0) { // we're the parent
- close(fds[1]); // parent keeps only the reading side
+ close(send_socket); // parent keeps only the reading side
string result;
- if (!ReadFileDescriptor(fds[0], &result)) {
+ bool success = ReadFrom(
+ [recv_socket](void* buf, int size) {
+ return read(recv_socket, buf, size);
+ },
+ &result);
+ close(recv_socket);
+ if (!success) {
pdie(blaze_exit_code::INTERNAL_ERROR, "Cannot read subprocess output");
}
-
return result;
- } else { // We're the child
- close(fds[0]); // child keeps only the writing side
+ } else { // We're the child
+ close(recv_socket); // child keeps only the writing side
// Redirect output to the writing side of the dup.
- dup2(fds[1], STDOUT_FILENO);
- dup2(fds[1], STDERR_FILENO);
+ dup2(send_socket, STDOUT_FILENO);
+ dup2(send_socket, STDERR_FILENO);
// Execute the binary
ExecuteProgram(exe, args_vector);
pdie(blaze_exit_code::INTERNAL_ERROR, "Failed to run %s", exe.c_str());
diff --git a/src/test/cpp/blaze_util_test.cc b/src/test/cpp/blaze_util_test.cc
index bb0faf0..9199ec3 100644
--- a/src/test/cpp/blaze_util_test.cc
+++ b/src/test/cpp/blaze_util_test.cc
@@ -91,14 +91,17 @@
return fds[0];
}
- static void AssertReadFileDescriptor2(string input1, string input2) {
+ static void AssertReadFrom2(string input1, string input2) {
int fd = WriteFileDescriptor2(input1, input2);
if (fd < 0) {
FAIL() << "Unable to create a pipe!";
} else {
string result;
- if (!ReadFileDescriptor(fd, &result)) {
- perror("ReadFileDescriptor");
+ bool success = ReadFrom(
+ [fd](void* buf, int size) { return read(fd, buf, size); }, &result);
+ close(fd);
+ if (!success) {
+ perror("ReadFrom");
FAIL() << "Unable to read file descriptor!";
} else {
ASSERT_EQ(input1 + input2, result);
@@ -106,22 +109,21 @@
}
}
- static void AssertReadFileDescriptor(string input) {
- AssertReadFileDescriptor2(input, "");
- }
+ static void AssertReadFrom(string input) { AssertReadFrom2(input, ""); }
static void AssertReadJvmVersion(string expected, string input) {
ASSERT_EQ(expected, ReadJvmVersion(input));
}
- void ReadFileDescriptorTest() const {
- AssertReadFileDescriptor("DummyJDK Blabla\n"
- "More DummyJDK Blabla\n");
- AssertReadFileDescriptor("dummyjdk version \"1.42.qual\"\n"
- "DummyJDK Blabla\n"
- "More DummyJDK Blabla\n");
- AssertReadFileDescriptor2("first_line\n",
- "second line version \"1.4.2_0\"\n");
+ void ReadFromTest() const {
+ AssertReadFrom(
+ "DummyJDK Blabla\n"
+ "More DummyJDK Blabla\n");
+ AssertReadFrom(
+ "dummyjdk version \"1.42.qual\"\n"
+ "DummyJDK Blabla\n"
+ "More DummyJDK Blabla\n");
+ AssertReadFrom2("first_line\n", "second line version \"1.4.2_0\"\n");
}
void ReadJvmVersionTest() const {
@@ -168,9 +170,7 @@
CheckJavaVersionIsAtLeastTest();
}
-TEST_F(BlazeUtilTest, ReadFileDescriptor) {
- ReadFileDescriptorTest();
-}
+TEST_F(BlazeUtilTest, ReadFrom) { ReadFromTest(); }
TEST_F(BlazeUtilTest, ReadJvmVersion) {
ReadJvmVersionTest();