summaryrefslogtreecommitdiffhomepage
path: root/test/util
diff options
context:
space:
mode:
Diffstat (limited to 'test/util')
-rw-r--r--test/util/BUILD239
-rw-r--r--test/util/capability_util.cc79
-rw-r--r--test/util/capability_util.h101
-rw-r--r--test/util/cleanup.h61
-rw-r--r--test/util/file_descriptor.h134
-rw-r--r--test/util/fs_util.cc585
-rw-r--r--test/util/fs_util.h182
-rw-r--r--test/util/fs_util_test.cc100
-rw-r--r--test/util/logging.cc97
-rw-r--r--test/util/logging.h73
-rw-r--r--test/util/memory_util.h124
-rw-r--r--test/util/mount_util.h48
-rw-r--r--test/util/multiprocess_util.cc139
-rw-r--r--test/util/multiprocess_util.h113
-rw-r--r--test/util/posix_error.cc93
-rw-r--r--test/util/posix_error.h428
-rw-r--r--test/util/posix_error_test.cc45
-rw-r--r--test/util/proc_util.cc98
-rw-r--r--test/util/proc_util.h150
-rw-r--r--test/util/save_util.cc59
-rw-r--r--test/util/save_util.h47
-rw-r--r--test/util/signal_util.cc103
-rw-r--r--test/util/signal_util.h92
-rw-r--r--test/util/temp_path.cc157
-rw-r--r--test/util/temp_path.h134
-rw-r--r--test/util/test_main.cc20
-rw-r--r--test/util/test_util.cc248
-rw-r--r--test/util/test_util.h794
-rw-r--r--test/util/test_util_test.cc250
-rw-r--r--test/util/thread_util.h89
-rw-r--r--test/util/timer_util.cc27
-rw-r--r--test/util/timer_util.h74
32 files changed, 4983 insertions, 0 deletions
diff --git a/test/util/BUILD b/test/util/BUILD
new file mode 100644
index 000000000..e4eec4ab9
--- /dev/null
+++ b/test/util/BUILD
@@ -0,0 +1,239 @@
+package(
+ default_visibility = ["//:sandbox"],
+ licenses = ["notice"], # Apache 2.0
+)
+
+cc_library(
+ name = "capability_util",
+ testonly = 1,
+ srcs = ["capability_util.cc"],
+ hdrs = ["capability_util.h"],
+ deps = [
+ ":cleanup",
+ ":memory_util",
+ ":posix_error",
+ ":save_util",
+ ":test_util",
+ "@com_google_absl//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "file_descriptor",
+ testonly = 1,
+ hdrs = ["file_descriptor.h"],
+ deps = [
+ ":logging",
+ ":posix_error",
+ ":save_util",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "proc_util",
+ testonly = 1,
+ srcs = ["proc_util.cc"],
+ hdrs = ["proc_util.h"],
+ deps = [
+ ":fs_util",
+ ":posix_error",
+ ":test_util",
+ "@com_google_absl//absl/strings",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "cleanup",
+ testonly = 1,
+ hdrs = ["cleanup.h"],
+)
+
+cc_library(
+ name = "fs_util",
+ testonly = 1,
+ srcs = ["fs_util.cc"],
+ hdrs = ["fs_util.h"],
+ deps = [
+ ":cleanup",
+ ":file_descriptor",
+ ":posix_error",
+ "@com_google_absl//absl/strings",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "fs_util_test",
+ size = "small",
+ srcs = ["fs_util_test.cc"],
+ deps = [
+ ":fs_util",
+ ":posix_error",
+ ":temp_path",
+ ":test_util",
+ ],
+)
+
+cc_library(
+ name = "logging",
+ testonly = 1,
+ srcs = ["logging.cc"],
+ hdrs = ["logging.h"],
+)
+
+cc_library(
+ name = "memory_util",
+ testonly = 1,
+ hdrs = ["memory_util.h"],
+ deps = [
+ ":logging",
+ ":posix_error",
+ ":save_util",
+ ":test_util",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ ],
+)
+
+cc_library(
+ name = "mount_util",
+ testonly = 1,
+ hdrs = ["mount_util.h"],
+ deps = [
+ ":cleanup",
+ ":posix_error",
+ ":test_util",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "save_util",
+ testonly = 1,
+ srcs = ["save_util.cc"],
+ hdrs = ["save_util.h"],
+)
+
+cc_library(
+ name = "multiprocess_util",
+ testonly = 1,
+ srcs = ["multiprocess_util.cc"],
+ hdrs = ["multiprocess_util.h"],
+ deps = [
+ ":cleanup",
+ ":file_descriptor",
+ ":posix_error",
+ ":save_util",
+ ":test_util",
+ "@com_google_absl//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "posix_error",
+ testonly = 1,
+ srcs = ["posix_error.cc"],
+ hdrs = ["posix_error.h"],
+ deps = [
+ ":logging",
+ "@com_google_absl//absl/base:core_headers",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:variant",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "posix_error_test",
+ size = "small",
+ srcs = ["posix_error_test.cc"],
+ deps = [":posix_error"],
+)
+
+cc_library(
+ name = "signal_util",
+ testonly = 1,
+ srcs = ["signal_util.cc"],
+ hdrs = ["signal_util.h"],
+ deps = [
+ ":cleanup",
+ ":posix_error",
+ ":test_util",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "temp_path",
+ testonly = 1,
+ srcs = ["temp_path.cc"],
+ hdrs = ["temp_path.h"],
+ deps = [
+ ":fs_util",
+ ":posix_error",
+ ":test_util",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/time",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "test_util",
+ testonly = 1,
+ srcs = ["test_util.cc"],
+ hdrs = ["test_util.h"],
+ deps = [
+ ":fs_util",
+ ":logging",
+ ":posix_error",
+ ":save_util",
+ "@com_github_gflags_gflags//:gflags",
+ "@com_google_absl//absl/base:core_headers",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ "@com_google_absl//absl/time",
+ "@com_google_glog//:glog",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "thread_util",
+ testonly = 1,
+ hdrs = ["thread_util.h"],
+ deps = [":logging"],
+)
+
+cc_library(
+ name = "timer_util",
+ testonly = 1,
+ srcs = ["timer_util.cc"],
+ hdrs = ["timer_util.h"],
+ deps = [
+ ":cleanup",
+ ":logging",
+ ":posix_error",
+ ":test_util",
+ "@com_google_absl//absl/time",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "test_util_test",
+ size = "small",
+ srcs = ["test_util_test.cc"],
+ deps = [":test_util"],
+)
+
+cc_library(
+ name = "test_main",
+ testonly = 1,
+ srcs = ["test_main.cc"],
+ deps = [":test_util"],
+)
diff --git a/test/util/capability_util.cc b/test/util/capability_util.cc
new file mode 100644
index 000000000..0656775d6
--- /dev/null
+++ b/test/util/capability_util.cc
@@ -0,0 +1,79 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/capability_util.h"
+
+#include <linux/capability.h>
+#include <sched.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include "absl/strings/str_cat.h"
+#include "test/util/memory_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+PosixErrorOr<bool> CanCreateUserNamespace() {
+ // The most reliable way to determine if userns creation is possible is by
+ // trying to create one; see below.
+ ASSIGN_OR_RETURN_ERRNO(
+ auto child_stack,
+ MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
+ int const child_pid =
+ clone(+[](void*) { return 0; },
+ reinterpret_cast<void*>(child_stack.addr() + kPageSize),
+ CLONE_NEWUSER | SIGCHLD, /* arg = */ nullptr);
+ if (child_pid > 0) {
+ int status;
+ int const ret = waitpid(child_pid, &status, /* options = */ 0);
+ MaybeSave();
+ if (ret < 0) {
+ return PosixError(errno, "waitpid");
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ return PosixError(
+ ESRCH, absl::StrCat("child process exited with status ", status));
+ }
+ return true;
+ } else if (errno == EPERM) {
+ // Per clone(2), EPERM can be returned if:
+ //
+ // - "CLONE_NEWUSER was specified in flags, but either the effective user ID
+ // or the effective group ID of the caller does not have a mapping in the
+ // parent namespace (see user_namespaces(7))."
+ //
+ // - "(since Linux 3.9) CLONE_NEWUSER was specified in flags and the caller
+ // is in a chroot environment (i.e., the caller's root directory does
+ // not match the root directory of the mount namespace in which it
+ // resides)."
+ LOG(INFO) << "clone(CLONE_NEWUSER) failed with EPERM";
+ return false;
+ } else if (errno == EUSERS) {
+ // "(since Linux 3.11) CLONE_NEWUSER was specified in flags, and the call
+ // would cause the limit on the number of nested user namespaces to be
+ // exceeded. See user_namespaces(7)."
+ LOG(INFO) << "clone(CLONE_NEWUSER) failed with EUSERS";
+ return false;
+ } else {
+ // Unexpected error code; indicate an actual error.
+ return PosixError(errno, "clone(CLONE_NEWUSER)");
+ }
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/capability_util.h b/test/util/capability_util.h
new file mode 100644
index 000000000..8708f5e69
--- /dev/null
+++ b/test/util/capability_util.h
@@ -0,0 +1,101 @@
+// Copyright 2018 Google LLC
+//
+// 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.
+
+// Utilities for testing capabilties.
+
+#ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_
+#define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_
+
+#include <errno.h>
+#include <linux/capability.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "test/util/cleanup.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+#ifndef _LINUX_CAPABILITY_VERSION_3
+#error Expecting _LINUX_CAPABILITY_VERSION_3 support
+#endif
+
+namespace gvisor {
+namespace testing {
+
+// HaveCapability returns true if the process has the specified EFFECTIVE
+// capability.
+inline PosixErrorOr<bool> HaveCapability(int cap) {
+ if (!cap_valid(cap)) {
+ return PosixError(EINVAL, "Invalid capability");
+ }
+
+ struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {};
+ RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps));
+ MaybeSave();
+
+ return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0;
+}
+
+// SetCapability sets the specified EFFECTIVE capability.
+inline PosixError SetCapability(int cap, bool set) {
+ if (!cap_valid(cap)) {
+ return PosixError(EINVAL, "Invalid capability");
+ }
+
+ struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {};
+ RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps));
+ MaybeSave();
+
+ if (set) {
+ caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap);
+ } else {
+ caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap);
+ }
+ header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps));
+ MaybeSave();
+
+ return NoError();
+}
+
+// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE
+// capabilities must be a subset of PERMITTED, so those are dropped as well.
+inline PosixError DropPermittedCapability(int cap) {
+ if (!cap_valid(cap)) {
+ return PosixError(EINVAL, "Invalid capability");
+ }
+
+ struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {};
+ RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps));
+ MaybeSave();
+
+ caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap);
+ caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap);
+
+ header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps));
+ MaybeSave();
+
+ return NoError();
+}
+
+PosixErrorOr<bool> CanCreateUserNamespace();
+
+} // namespace testing
+} // namespace gvisor
+#endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_
diff --git a/test/util/cleanup.h b/test/util/cleanup.h
new file mode 100644
index 000000000..fb4724f97
--- /dev/null
+++ b/test/util/cleanup.h
@@ -0,0 +1,61 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_CLEANUP_H_
+#define GVISOR_TEST_UTIL_CLEANUP_H_
+
+#include <functional>
+#include <utility>
+
+namespace gvisor {
+namespace testing {
+
+class Cleanup {
+ public:
+ Cleanup() : released_(true) {}
+ explicit Cleanup(std::function<void()>&& callback) : cb_(callback) {}
+
+ Cleanup(Cleanup&& other) {
+ released_ = other.released_;
+ cb_ = other.Release();
+ }
+
+ Cleanup& operator=(Cleanup&& other) {
+ released_ = other.released_;
+ cb_ = other.Release();
+ return *this;
+ }
+
+ ~Cleanup() {
+ if (!released_) {
+ cb_();
+ }
+ }
+
+ std::function<void()>&& Release() {
+ released_ = true;
+ return std::move(cb_);
+ }
+
+ private:
+ Cleanup(Cleanup const& other) = delete;
+
+ bool released_ = false;
+ std::function<void(void)> cb_;
+};
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_CLEANUP_H_
diff --git a/test/util/file_descriptor.h b/test/util/file_descriptor.h
new file mode 100644
index 000000000..be8812d01
--- /dev/null
+++ b/test/util/file_descriptor.h
@@ -0,0 +1,134 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_
+#define GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "test/util/logging.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// FileDescriptor is an RAII type class which takes ownership of a file
+// descriptor. It will close the FD when this object goes out of scope.
+class FileDescriptor {
+ public:
+ // Constructs an empty FileDescriptor (one that does not own a file
+ // descriptor).
+ FileDescriptor() = default;
+
+ // Constructs a FileDescriptor that owns fd. If fd is negative, constructs an
+ // empty FileDescriptor.
+ explicit FileDescriptor(int fd) { set_fd(fd); }
+
+ FileDescriptor(FileDescriptor&& orig) : fd_(orig.release()) {}
+
+ FileDescriptor& operator=(FileDescriptor&& orig) {
+ reset(orig.release());
+ return *this;
+ }
+
+ PosixErrorOr<FileDescriptor> Dup() const {
+ if (fd_ < 0) {
+ return PosixError(EINVAL, "Attempting to Dup unset fd");
+ }
+
+ int fd = dup(fd_);
+ if (fd < 0) {
+ return PosixError(errno, absl::StrCat("dup ", fd_));
+ }
+ MaybeSave();
+ return FileDescriptor(fd);
+ }
+
+ FileDescriptor(FileDescriptor const& other) = delete;
+ FileDescriptor& operator=(FileDescriptor const& other) = delete;
+
+ ~FileDescriptor() { reset(); }
+
+ // If this object is non-empty, returns the owned file descriptor. (Ownership
+ // is retained by the FileDescriptor.) Otherwise returns -1.
+ int get() const { return fd_; }
+
+ // If this object is non-empty, transfers ownership of the file descriptor to
+ // the caller and returns it. Otherwise returns -1.
+ int release() {
+ int const fd = fd_;
+ fd_ = -1;
+ return fd;
+ }
+
+ // If this object is non-empty, closes the owned file descriptor (recording a
+ // test failure if the close fails).
+ void reset() { reset(-1); }
+
+ // Like no-arg reset(), but the FileDescriptor takes ownership of fd after
+ // closing its existing file descriptor.
+ void reset(int fd) {
+ if (fd_ >= 0) {
+ TEST_PCHECK(close(fd_) == 0);
+ MaybeSave();
+ }
+ set_fd(fd);
+ }
+
+ private:
+ // Wrapper that coerces negative fd values other than -1 to -1 so that get()
+ // etc. return -1.
+ void set_fd(int fd) { fd_ = std::max(fd, -1); }
+
+ int fd_ = -1;
+};
+
+// Wrapper around open(2) that returns a FileDescriptor.
+inline PosixErrorOr<FileDescriptor> Open(std::string const& path, int flags,
+ mode_t mode = 0) {
+ int fd = open(path.c_str(), flags, mode);
+ if (fd < 0) {
+ return PosixError(errno, absl::StrFormat("open(%s, %#x, %#o)", path.c_str(),
+ flags, mode));
+ }
+ MaybeSave();
+ return FileDescriptor(fd);
+}
+
+// Wrapper around openat(2) that returns a FileDescriptor.
+inline PosixErrorOr<FileDescriptor> OpenAt(int dirfd, std::string const& path,
+ int flags, mode_t mode = 0) {
+ int fd = openat(dirfd, path.c_str(), flags, mode);
+ if (fd < 0) {
+ return PosixError(errno, absl::StrFormat("openat(%d, %s, %#x, %#o)", dirfd,
+ path, flags, mode));
+ }
+ MaybeSave();
+ return FileDescriptor(fd);
+}
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_
diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc
new file mode 100644
index 000000000..e7e8be1d8
--- /dev/null
+++ b/test/util/fs_util.cc
@@ -0,0 +1,585 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/fs_util.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "gmock/gmock.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "test/util/cleanup.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+PosixError WriteContentsToFD(int fd, absl::string_view contents) {
+ int written = 0;
+ while (static_cast<absl::string_view::size_type>(written) < contents.size()) {
+ int wrote = write(fd, contents.data() + written, contents.size() - written);
+ if (wrote < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return PosixError(
+ errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure."));
+ }
+ written += wrote;
+ }
+ return NoError();
+}
+} // namespace
+
+namespace internal {
+
+// Given a collection of file paths, append them all together,
+// ensuring that the proper path separators are inserted between them.
+std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) {
+ std::string result;
+
+ if (paths.size() != 0) {
+ // This size calculation is worst-case: it assumes one extra "/" for every
+ // path other than the first.
+ size_t total_size = paths.size() - 1;
+ for (const absl::string_view path : paths) total_size += path.size();
+ result.resize(total_size);
+
+ auto begin = result.begin();
+ auto out = begin;
+ bool trailing_slash = false;
+ for (absl::string_view path : paths) {
+ if (path.empty()) continue;
+ if (path.front() == '/') {
+ if (trailing_slash) {
+ path.remove_prefix(1);
+ }
+ } else {
+ if (!trailing_slash && out != begin) *out++ = '/';
+ }
+ const size_t this_size = path.size();
+ memcpy(&*out, path.data(), this_size);
+ out += this_size;
+ trailing_slash = out[-1] == '/';
+ }
+ result.erase(out - begin);
+ }
+ return result;
+}
+} // namespace internal
+
+// Returns a status or the current working directory.
+PosixErrorOr<std::string> GetCWD() {
+ char buffer[PATH_MAX + 1] = {};
+ if (getcwd(buffer, PATH_MAX) == nullptr) {
+ return PosixError(errno, "GetCWD() failed");
+ }
+
+ return std::string(buffer);
+}
+
+PosixErrorOr<struct stat> Stat(absl::string_view path) {
+ struct stat stat_buf;
+ int res = stat(std::string(path).c_str(), &stat_buf);
+ if (res < 0) {
+ return PosixError(errno, absl::StrCat("stat ", path));
+ }
+ return stat_buf;
+}
+
+PosixErrorOr<bool> Exists(absl::string_view path) {
+ struct stat stat_buf;
+ int res = stat(std::string(path).c_str(), &stat_buf);
+ if (res < 0) {
+ if (errno == ENOENT) {
+ return false;
+ }
+ return PosixError(errno, absl::StrCat("stat ", path));
+ }
+ return true;
+}
+
+PosixErrorOr<bool> IsDirectory(absl::string_view path) {
+ ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Stat(path));
+ if (S_ISDIR(stat_buf.st_mode)) {
+ return true;
+ }
+
+ return false;
+}
+
+PosixError Delete(absl::string_view path) {
+ int res = unlink(std::string(path).c_str());
+ if (res < 0) {
+ return PosixError(errno, absl::StrCat("unlink ", path));
+ }
+
+ return NoError();
+}
+
+PosixError Truncate(absl::string_view path, int length) {
+ int res = truncate(std::string(path).c_str(), length);
+ if (res < 0) {
+ return PosixError(errno,
+ absl::StrCat("truncate ", path, " to length ", length));
+ }
+
+ return NoError();
+}
+
+PosixError Chmod(absl::string_view path, int mode) {
+ int res = chmod(std::string(path).c_str(), mode);
+ if (res < 0) {
+ return PosixError(errno, absl::StrCat("chmod ", path));
+ }
+
+ return NoError();
+}
+
+PosixError Mkdir(absl::string_view path, int mode) {
+ int res = mkdir(std::string(path).c_str(), mode);
+ if (res < 0) {
+ return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode));
+ }
+
+ return NoError();
+}
+
+PosixError Rmdir(absl::string_view path) {
+ int res = rmdir(std::string(path).c_str());
+ if (res < 0) {
+ return PosixError(errno, absl::StrCat("rmdir ", path));
+ }
+
+ return NoError();
+}
+
+PosixError SetContents(absl::string_view path, absl::string_view contents) {
+ ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
+ if (!exists) {
+ return PosixError(
+ ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist."));
+ }
+
+ ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC));
+ return WriteContentsToFD(fd.get(), contents);
+}
+
+// Create a file with the given contents (if it does not already exist with the
+// given mode) and then set the contents.
+PosixError CreateWithContents(absl::string_view path,
+ absl::string_view contents, int mode) {
+ ASSIGN_OR_RETURN_ERRNO(
+ auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode));
+ return WriteContentsToFD(fd.get(), contents);
+}
+
+PosixError GetContents(absl::string_view path, std::string* output) {
+ ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY));
+ output->clear();
+
+ // Keep reading until we hit an EOF or an error.
+ return GetContentsFD(fd.get(), output);
+}
+
+PosixErrorOr<std::string> GetContents(absl::string_view path) {
+ std::string ret;
+ RETURN_IF_ERRNO(GetContents(path, &ret));
+ return ret;
+}
+
+PosixErrorOr<std::string> GetContentsFD(int fd) {
+ std::string ret;
+ RETURN_IF_ERRNO(GetContentsFD(fd, &ret));
+ return ret;
+}
+
+PosixError GetContentsFD(int fd, std::string* output) {
+ // Keep reading until we hit an EOF or an error.
+ while (true) {
+ char buf[16 * 1024] = {}; // Read in 16KB chunks.
+ int bytes_read = read(fd, buf, sizeof(buf));
+ if (bytes_read < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return PosixError(errno, "GetContentsFD read failure.");
+ }
+
+ if (bytes_read == 0) {
+ break; // EOF.
+ }
+
+ output->append(buf, bytes_read);
+ }
+ return NoError();
+}
+
+PosixErrorOr<std::string> ReadLink(absl::string_view path) {
+ char buf[PATH_MAX + 1] = {};
+ int ret = readlink(std::string(path).c_str(), buf, PATH_MAX);
+ if (ret < 0) {
+ return PosixError(errno, absl::StrCat("readlink ", path));
+ }
+
+ return std::string(buf, ret);
+}
+
+PosixError WalkTree(
+ absl::string_view path, bool recursive,
+ const std::function<void(absl::string_view, const struct stat&)>& cb) {
+ DIR* dir = opendir(std::string(path).c_str());
+ if (dir == nullptr) {
+ return PosixError(errno, absl::StrCat("opendir ", path));
+ }
+ auto dir_closer = Cleanup([&dir]() { closedir(dir); });
+ while (true) {
+ // Readdir(3): If the end of the directory stream is reached, NULL is
+ // returned and errno is not changed. If an error occurs, NULL is returned
+ // and errno is set appropriately. To distinguish end of stream and from an
+ // error, set errno to zero before calling readdir() and then check the
+ // value of errno if NULL is returned.
+ errno = 0;
+ struct dirent* dp = readdir(dir);
+ if (dp == nullptr) {
+ if (errno != 0) {
+ return PosixError(errno, absl::StrCat("readdir ", path));
+ }
+ break; // We're done.
+ }
+
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
+ // Skip dots.
+ continue;
+ }
+
+ auto full_path = JoinPath(path, dp->d_name);
+ ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path));
+ if (S_ISDIR(s.st_mode) && recursive) {
+ RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb));
+ } else {
+ cb(full_path, s);
+ }
+ }
+ // We're done walking so let's invoke our cleanup callback now.
+ dir_closer.Release()();
+
+ // And we have to dispatch the callback on the base directory.
+ ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path));
+ cb(path, s);
+
+ return NoError();
+}
+
+PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath,
+ bool skipdots) {
+ std::vector<std::string> files;
+
+ DIR* dir = opendir(std::string(abspath).c_str());
+ if (dir == nullptr) {
+ return PosixError(errno, absl::StrCat("opendir ", abspath));
+ }
+ auto dir_closer = Cleanup([&dir]() { closedir(dir); });
+ while (true) {
+ // Readdir(3): If the end of the directory stream is reached, NULL is
+ // returned and errno is not changed. If an error occurs, NULL is returned
+ // and errno is set appropriately. To distinguish end of stream and from an
+ // error, set errno to zero before calling readdir() and then check the
+ // value of errno if NULL is returned.
+ errno = 0;
+ struct dirent* dp = readdir(dir);
+ if (dp == nullptr) {
+ if (errno != 0) {
+ return PosixError(errno, absl::StrCat("readdir ", abspath));
+ }
+ break; // We're done.
+ }
+
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
+ if (skipdots) {
+ continue;
+ }
+ }
+ files.push_back(std::string(dp->d_name));
+ }
+
+ return files;
+}
+
+PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs,
+ int* undeleted_files) {
+ ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
+ if (!exists) {
+ return PosixError(ENOENT, absl::StrCat(path, " does not exist"));
+ }
+
+ ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path));
+ if (!dir) {
+ // Nothing recursive needs to happen we can just call Delete.
+ auto status = Delete(path);
+ if (!status.ok() && undeleted_files) {
+ (*undeleted_files)++;
+ }
+ return status;
+ }
+
+ return WalkTree(path, /*recursive=*/true,
+ [&](absl::string_view absolute_path, const struct stat& s) {
+ if (S_ISDIR(s.st_mode)) {
+ auto rm_status = Rmdir(absolute_path);
+ if (!rm_status.ok() && undeleted_dirs) {
+ (*undeleted_dirs)++;
+ }
+ } else {
+ auto delete_status = Delete(absolute_path);
+ if (!delete_status.ok() && undeleted_files) {
+ (*undeleted_files)++;
+ }
+ }
+ });
+}
+
+PosixError RecursivelyCreateDir(absl::string_view path) {
+ if (path.empty() || path == "/") {
+ return PosixError(EINVAL, "Cannot create root!");
+ }
+
+ // Does it already exist, if so we're done.
+ ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
+ if (exists) {
+ return NoError();
+ }
+
+ // Do we need to create directories under us?
+ auto dirname = Dirname(path);
+ ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname));
+ if (!exists) {
+ RETURN_IF_ERRNO(RecursivelyCreateDir(dirname));
+ }
+
+ return Mkdir(path);
+}
+
+// Makes a path absolute with respect to an optional base. If no base is
+// provided it will use the current working directory.
+PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename,
+ absl::string_view base) {
+ if (filename.empty()) {
+ return PosixError(EINVAL, "filename cannot be empty.");
+ }
+
+ if (filename[0] == '/') {
+ // This path is already absolute.
+ return std::string(filename);
+ }
+
+ std::string actual_base;
+ if (!base.empty()) {
+ actual_base = std::string(base);
+ } else {
+ auto cwd_or = GetCWD();
+ RETURN_IF_ERRNO(cwd_or.error());
+ actual_base = cwd_or.ValueOrDie();
+ }
+
+ // Reverse iterate removing trailing slashes, effectively right trim '/'.
+ for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) {
+ actual_base.erase(i, 1);
+ }
+
+ if (filename == ".") {
+ return actual_base.empty() ? "/" : actual_base;
+ }
+
+ return absl::StrCat(actual_base, "/", filename);
+}
+
+std::string CleanPath(const absl::string_view unclean_path) {
+ std::string path = std::string(unclean_path);
+ const char *src = path.c_str();
+ std::string::iterator dst = path.begin();
+
+ // Check for absolute path and determine initial backtrack limit.
+ const bool is_absolute_path = *src == '/';
+ if (is_absolute_path) {
+ *dst++ = *src++;
+ while (*src == '/') ++src;
+ }
+ std::string::const_iterator backtrack_limit = dst;
+
+ // Process all parts
+ while (*src) {
+ bool parsed = false;
+
+ if (src[0] == '.') {
+ // 1dot ".<whateverisnext>", check for END or SEP.
+ if (src[1] == '/' || !src[1]) {
+ if (*++src) {
+ ++src;
+ }
+ parsed = true;
+ } else if (src[1] == '.' && (src[2] == '/' || !src[2])) {
+ // 2dot END or SEP (".." | "../<whateverisnext>").
+ src += 2;
+ if (dst != backtrack_limit) {
+ // We can backtrack the previous part
+ for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) {
+ // Empty.
+ }
+ } else if (!is_absolute_path) {
+ // Failed to backtrack and we can't skip it either. Rewind and copy.
+ src -= 2;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ if (*src) {
+ *dst++ = *src;
+ }
+ // We can never backtrack over a copied "../" part so set new limit.
+ backtrack_limit = dst;
+ }
+ if (*src) {
+ ++src;
+ }
+ parsed = true;
+ }
+ }
+
+ // If not parsed, copy entire part until the next SEP or EOS.
+ if (!parsed) {
+ while (*src && *src != '/') {
+ *dst++ = *src++;
+ }
+ if (*src) {
+ *dst++ = *src++;
+ }
+ }
+
+ // Skip consecutive SEP occurrences
+ while (*src == '/') {
+ ++src;
+ }
+ }
+
+ // Calculate and check the length of the cleaned path.
+ int path_length = dst - path.begin();
+ if (path_length != 0) {
+ // Remove trailing '/' except if it is root path ("/" ==> path_length := 1)
+ if (path_length > 1 && path[path_length - 1] == '/') {
+ --path_length;
+ }
+ path.resize(path_length);
+ } else {
+ // The cleaned path is empty; assign "." as per the spec.
+ path.assign(1, '.');
+ }
+ return path;
+}
+
+PosixErrorOr<std::string> GetRelativePath(absl::string_view source,
+ absl::string_view dest) {
+ if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) {
+ // At least one of the inputs is not an absolute path.
+ return PosixError(
+ EINVAL,
+ "GetRelativePath: At least one of the inputs is not an absolute path.");
+ }
+ const std::string clean_source = CleanPath(source);
+ const std::string clean_dest = CleanPath(dest);
+ auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty());
+ auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty());
+ auto source_iter = source_parts.begin();
+ auto dest_iter = dest_parts.begin();
+
+ // Advance past common prefix.
+ while (source_iter != source_parts.end() && dest_iter != dest_parts.end() &&
+ *source_iter == *dest_iter) {
+ ++source_iter;
+ ++dest_iter;
+ }
+
+ // Build result backtracking.
+ std::string result = "";
+ while (source_iter != source_parts.end()) {
+ absl::StrAppend(&result, "../");
+ ++source_iter;
+ }
+
+ // Add remaining path to dest.
+ while (dest_iter != dest_parts.end()) {
+ absl::StrAppend(&result, *dest_iter, "/");
+ ++dest_iter;
+ }
+
+ if (result.empty()) {
+ return std::string(".");
+ }
+
+ // Remove trailing slash.
+ result.erase(result.size() - 1);
+ return result;
+}
+
+absl::string_view Dirname(absl::string_view path) {
+ return SplitPath(path).first;
+}
+
+absl::string_view Basename(absl::string_view path) {
+ return SplitPath(path).second;
+}
+
+std::pair<absl::string_view, absl::string_view> SplitPath(
+ absl::string_view path) {
+ std::string::size_type pos = path.find_last_of('/');
+
+ // Handle the case with no '/' in 'path'.
+ if (pos == absl::string_view::npos)
+ return std::make_pair(path.substr(0, 0), path);
+
+ // Handle the case with a single leading '/' in 'path'.
+ if (pos == 0)
+ return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1));
+
+ return std::make_pair(path.substr(0, pos),
+ absl::ClippedSubstr(path, pos + 1));
+}
+
+std::string JoinPath(absl::string_view path1, absl::string_view path2) {
+ if (path1.empty()) return std::string(path2);
+ if (path2.empty()) return std::string(path1);
+ if (path1.back() == '/') {
+ if (path2.front() == '/')
+ return absl::StrCat(path1, absl::ClippedSubstr(path2, 1));
+ } else {
+ if (path2.front() != '/') return absl::StrCat(path1, "/", path2);
+ }
+ return absl::StrCat(path1, path2);
+}
+
+PosixErrorOr<std::string> ProcessExePath(int pid) {
+ if (pid <= 0) {
+ return PosixError(EINVAL, "Invalid pid specified");
+ }
+
+ return ReadLink(absl::StrCat("/proc/", pid, "/exe"));
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/fs_util.h b/test/util/fs_util.h
new file mode 100644
index 000000000..9412b2f71
--- /dev/null
+++ b/test/util/fs_util.h
@@ -0,0 +1,182 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_FS_UTIL_H_
+#define GVISOR_TEST_UTIL_FS_UTIL_H_
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "absl/strings/string_view.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+// Returns a status or the current working directory.
+PosixErrorOr<std::string> GetCWD();
+
+// Returns true/false depending on whether or not path exists, or an error if it
+// can't be determined.
+PosixErrorOr<bool> Exists(absl::string_view path);
+
+// Returns a stat structure for the given path or an error.
+PosixErrorOr<struct stat> Stat(absl::string_view path);
+
+// Deletes the file or directory at path or returns an error.
+PosixError Delete(absl::string_view path);
+
+// Changes the mode of a file or returns an error.
+PosixError Chmod(absl::string_view path, int mode);
+
+// Truncates a file to the given length or returns an error.
+PosixError Truncate(absl::string_view path, int length);
+
+// Returns true/false depending on whether or not the path is a directory or
+// returns an error.
+PosixErrorOr<bool> IsDirectory(absl::string_view path);
+
+// Makes a directory or returns an error.
+PosixError Mkdir(absl::string_view path, int mode = 0755);
+
+// Removes a directory or returns an error.
+PosixError Rmdir(absl::string_view path);
+
+// Attempts to set the contents of a file or returns an error.
+PosixError SetContents(absl::string_view path, absl::string_view contents);
+
+// Creates a file with the given contents and mode or returns an error.
+PosixError CreateWithContents(absl::string_view path,
+ absl::string_view contents, int mode = 0666);
+
+// Attempts to read the entire contents of the file into the provided std::string
+// buffer or returns an error.
+PosixError GetContents(absl::string_view path, std::string* output);
+
+// Attempts to read the entire contents of the file or returns an error.
+PosixErrorOr<std::string> GetContents(absl::string_view path);
+
+// Attempts to read the entire contents of the provided fd into the provided
+// std::string or returns an error.
+PosixError GetContentsFD(int fd, std::string* output);
+
+// Attempts to read the entire contents of the provided fd or returns an error.
+PosixErrorOr<std::string> GetContentsFD(int fd);
+
+// Executes the readlink(2) system call or returns an error.
+PosixErrorOr<std::string> ReadLink(absl::string_view path);
+
+// WalkTree will walk a directory tree in a depth first search manner (if
+// recursive). It will invoke a provided callback for each file and directory,
+// the parent will always be invoked last making this appropriate for things
+// such as deleting an entire directory tree.
+//
+// This method will return an error when it's unable to access the provided
+// path, or when the path is not a directory.
+PosixError WalkTree(
+ absl::string_view path, bool recursive,
+ const std::function<void(absl::string_view, const struct stat&)>& cb);
+
+// Returns the base filenames for all files under a given absolute path. If
+// skipdots is true the returned vector will not contain "." or "..". This
+// method does not walk the tree recursively it only returns the elements
+// in that directory.
+PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath,
+ bool skipdots);
+
+// Attempt to recursively delete a directory or file. Returns an error and
+// the number of undeleted directories and files. If either
+// undeleted_dirs or undeleted_files is nullptr then it will not be used.
+PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs,
+ int* undeleted_files);
+
+// Recursively create the directory provided or return an error.
+PosixError RecursivelyCreateDir(absl::string_view path);
+
+// Makes a path absolute with respect to an optional base. If no base is
+// provided it will use the current working directory.
+PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename,
+ absl::string_view base);
+
+// Generates a relative path from the source directory to the destination
+// (dest) file or directory. This uses ../ when necessary for destinations
+// which are not nested within the source. Both source and dest are required
+// to be absolute paths, and an empty std::string will be returned if they are not.
+PosixErrorOr<std::string> GetRelativePath(absl::string_view source,
+ absl::string_view dest);
+
+// Returns the part of the path before the final "/", EXCEPT:
+// * If there is a single leading "/" in the path, the result will be the
+// leading "/".
+// * If there is no "/" in the path, the result is the empty prefix of the
+// input std::string.
+absl::string_view Dirname(absl::string_view path);
+
+// Return the parts of the path, split on the final "/". If there is no
+// "/" in the path, the first part of the output is empty and the second
+// is the input. If the only "/" in the path is the first character, it is
+// the first part of the output.
+std::pair<absl::string_view, absl::string_view> SplitPath(
+ absl::string_view path);
+
+// Returns the part of the path after the final "/". If there is no
+// "/" in the path, the result is the same as the input.
+// Note that this function's behavior differs from the Unix basename
+// command if path ends with "/". For such paths, this function returns the
+// empty std::string.
+absl::string_view Basename(absl::string_view path);
+
+// Collapse duplicate "/"s, resolve ".." and "." path elements, remove
+// trailing "/".
+//
+// NOTE: This respects relative vs. absolute paths, but does not
+// invoke any system calls (getcwd(2)) in order to resolve relative
+// paths wrt actual working directory. That is, this is purely a
+// std::string manipulation, completely independent of process state.
+std::string CleanPath(absl::string_view path);
+
+// Returns the full path to the executable of the given pid or a PosixError.
+PosixErrorOr<std::string> ProcessExePath(int pid);
+
+namespace internal {
+// Not part of the public API.
+std::string JoinPathImpl(std::initializer_list<absl::string_view> paths);
+} // namespace internal
+
+// Join multiple paths together.
+// All paths will be treated as relative paths, regardless of whether or not
+// they start with a leading '/'. That is, all paths will be concatenated
+// together, with the appropriate path separator inserted in between.
+// Arguments must be convertible to absl::string_view.
+//
+// Usage:
+// std::string path = JoinPath("/foo", dirname, filename);
+// std::string path = JoinPath(FLAGS_test_srcdir, filename);
+//
+// 0, 1, 2-path specializations exist to optimize common cases.
+inline std::string JoinPath() { return std::string(); }
+inline std::string JoinPath(absl::string_view path) {
+ return std::string(path.data(), path.size());
+}
+
+std::string JoinPath(absl::string_view path1, absl::string_view path2);
+template <typename... T>
+inline std::string JoinPath(absl::string_view path1, absl::string_view path2,
+ absl::string_view path3, const T&... args) {
+ return internal::JoinPathImpl({path1, path2, path3, args...});
+}
+} // namespace testing
+} // namespace gvisor
+#endif // GVISOR_TEST_UTIL_FS_UTIL_H_
diff --git a/test/util/fs_util_test.cc b/test/util/fs_util_test.cc
new file mode 100644
index 000000000..ce70d58aa
--- /dev/null
+++ b/test/util/fs_util_test.cc
@@ -0,0 +1,100 @@
+// Copyright 2018 Google LLC
+//
+// 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 <errno.h>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/util/fs_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+TEST(FsUtilTest, RecursivelyCreateDirManualDelete) {
+ const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const std::string base_path = JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m");
+
+ ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false));
+ ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path));
+
+ // Delete everything until we hit root and then stop, we want to try this
+ // without using RecursivelyDelete.
+ std::string cur_path = base_path;
+ while (cur_path != root.path()) {
+ ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(true));
+ ASSERT_NO_ERRNO(Rmdir(cur_path));
+ ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(false));
+ auto dir = Dirname(cur_path);
+ cur_path = std::string(dir);
+ }
+}
+
+TEST(FsUtilTest, RecursivelyCreateAndDeleteDir) {
+ const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const std::string base_path = JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m");
+
+ ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false));
+ ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path));
+
+ const std::string sub_path = JoinPath(root.path(), "a");
+ ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr));
+ ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false));
+}
+
+TEST(FsUtilTest, RecursivelyCreateAndDeletePartial) {
+ const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const std::string base_path = JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m");
+
+ ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false));
+ ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path));
+
+ const std::string a = JoinPath(root.path(), "a");
+ auto listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, true));
+ ASSERT_THAT(listing, ::testing::Contains("b"));
+ ASSERT_EQ(listing.size(), 1);
+
+ listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, false));
+ ASSERT_THAT(listing, ::testing::Contains("."));
+ ASSERT_THAT(listing, ::testing::Contains(".."));
+ ASSERT_THAT(listing, ::testing::Contains("b"));
+ ASSERT_EQ(listing.size(), 3);
+
+ const std::string sub_path = JoinPath(root.path(), "/a/b/c/d/e/f");
+
+ ASSERT_NO_ERRNO(
+ CreateWithContents(JoinPath(Dirname(sub_path), "file"), "Hello World"));
+ std::string contents = "";
+ ASSERT_NO_ERRNO(GetContents(JoinPath(Dirname(sub_path), "file"), &contents));
+ ASSERT_EQ(contents, "Hello World");
+
+ ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr));
+ ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false));
+
+ // The parent of the subpath (directory e) should still exist.
+ ASSERT_THAT(Exists(Dirname(sub_path)), IsPosixErrorOkAndHolds(true));
+
+ // The file we created along side f should also still exist.
+ ASSERT_THAT(Exists(JoinPath(Dirname(sub_path), "file")),
+ IsPosixErrorOkAndHolds(true));
+}
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/logging.cc b/test/util/logging.cc
new file mode 100644
index 000000000..86ea71df3
--- /dev/null
+++ b/test/util/logging.cc
@@ -0,0 +1,97 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/logging.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// We implement this here instead of using test_util to avoid cyclic
+// dependencies.
+int Write(int fd, const char* buf, size_t size) {
+ size_t written = 0;
+ while (written < size) {
+ int res = write(fd, buf + written, size - written);
+ if (res < 0 && errno == EINTR) {
+ continue;
+ } else if (res <= 0) {
+ break;
+ }
+
+ written += res;
+ }
+ return static_cast<int>(written);
+}
+
+// Write 32-bit decimal number to fd.
+int WriteNumber(int fd, uint32_t val) {
+ constexpr char kDigits[] = "0123456789";
+ constexpr int kBase = 10;
+
+ // 10 chars for 32-bit number in decimal, 1 char for the NUL-terminator.
+ constexpr int kBufferSize = 11;
+ char buf[kBufferSize];
+
+ // Convert the number to std::string.
+ char* s = buf + sizeof(buf) - 1;
+ size_t size = 0;
+
+ *s = '\0';
+ do {
+ s--;
+ size++;
+
+ *s = kDigits[val % kBase];
+ val /= kBase;
+ } while (val);
+
+ return Write(fd, s, size);
+}
+
+} // namespace
+
+void CheckFailure(const char* cond, size_t cond_size, const char* msg,
+ size_t msg_size, bool include_errno) {
+ int saved_errno = errno;
+
+ constexpr char kCheckFailure[] = "Check failed: ";
+ Write(2, kCheckFailure, sizeof(kCheckFailure) - 1);
+ Write(2, cond, cond_size);
+
+ if (msg != nullptr) {
+ Write(2, ": ", 2);
+ Write(2, msg, msg_size);
+ }
+
+ if (include_errno) {
+ constexpr char kErrnoMessage[] = " (errno ";
+ Write(2, kErrnoMessage, sizeof(kErrnoMessage) - 1);
+ WriteNumber(2, saved_errno);
+ Write(2, ")", 1);
+ }
+
+ Write(2, "\n", 1);
+
+ abort();
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/logging.h b/test/util/logging.h
new file mode 100644
index 000000000..6e957b172
--- /dev/null
+++ b/test/util/logging.h
@@ -0,0 +1,73 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_LOGGING_H_
+#define GVISOR_TEST_UTIL_LOGGING_H_
+
+#include <stddef.h>
+
+namespace gvisor {
+namespace testing {
+
+void CheckFailure(const char* cond, size_t cond_size, const char* msg,
+ size_t msg_size, bool include_errno);
+
+// If cond is false, aborts the current process.
+//
+// This macro is async-signal-safe.
+#define TEST_CHECK(cond) \
+ do { \
+ if (!(cond)) { \
+ ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \
+ 0, false); \
+ } \
+ } while (0)
+
+// If cond is false, logs msg then aborts the current process.
+//
+// This macro is async-signal-safe.
+#define TEST_CHECK_MSG(cond, msg) \
+ do { \
+ if (!(cond)) { \
+ ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \
+ sizeof(msg) - 1, false); \
+ } \
+ } while (0)
+
+// If cond is false, logs errno, then aborts the current process.
+//
+// This macro is async-signal-safe.
+#define TEST_PCHECK(cond) \
+ do { \
+ if (!(cond)) { \
+ ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \
+ 0, true); \
+ } \
+ } while (0)
+
+// If cond is false, logs msg and errno, then aborts the current process.
+//
+// This macro is async-signal-safe.
+#define TEST_PCHECK_MSG(cond, msg) \
+ do { \
+ if (!(cond)) { \
+ ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \
+ sizeof(msg) - 1, true); \
+ } \
+ } while (0)
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_LOGGING_H_
diff --git a/test/util/memory_util.h b/test/util/memory_util.h
new file mode 100644
index 000000000..8f6e99ba6
--- /dev/null
+++ b/test/util/memory_util.h
@@ -0,0 +1,124 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_MEMORY_UTIL_H_
+#define GVISOR_TEST_UTIL_MEMORY_UTIL_H_
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/mman.h>
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "test/util/logging.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// RAII type for mmap'ed memory. Only usable in tests due to use of a test-only
+// macro that can't be named without invoking the presubmit's wrath.
+class Mapping {
+ public:
+ // Constructs a mapping that owns nothing.
+ Mapping() = default;
+
+ // Constructs a mapping that owns the mmapped memory [ptr, ptr+len). Most
+ // users should use Mmap or MmapAnon instead.
+ Mapping(void* ptr, size_t len) : ptr_(ptr), len_(len) {}
+
+ Mapping(Mapping&& orig) : ptr_(orig.ptr_), len_(orig.len_) { orig.release(); }
+
+ Mapping& operator=(Mapping&& orig) {
+ ptr_ = orig.ptr_;
+ len_ = orig.len_;
+ orig.release();
+ return *this;
+ }
+
+ Mapping(Mapping const&) = delete;
+ Mapping& operator=(Mapping const&) = delete;
+
+ ~Mapping() { reset(); }
+
+ void* ptr() const { return ptr_; }
+ size_t len() const { return len_; }
+
+ // Returns a pointer to the end of the mapping. Useful for when the mapping
+ // is used as a thread stack.
+ void* endptr() const { return reinterpret_cast<void*>(addr() + len_); }
+
+ // Returns the start of this mapping cast to uintptr_t for ease of pointer
+ // arithmetic.
+ uintptr_t addr() const { return reinterpret_cast<uintptr_t>(ptr_); }
+
+ // Returns the end of this mapping cast to uintptr_t for ease of pointer
+ // arithmetic.
+ uintptr_t endaddr() const { return reinterpret_cast<uintptr_t>(endptr()); }
+
+ // Returns this mapping as a StringPiece for ease of comparison.
+ //
+ // This function is named view in anticipation of the eventual replacement of
+ // StringPiece with std::string_view.
+ absl::string_view view() const {
+ return absl::string_view(static_cast<char const*>(ptr_), len_);
+ }
+
+ // These are both named reset for consistency with standard smart pointers.
+
+ void reset(void* ptr, size_t len) {
+ if (len_) {
+ TEST_PCHECK(munmap(ptr_, len_) == 0);
+ }
+ ptr_ = ptr;
+ len_ = len;
+ }
+
+ void reset() { reset(nullptr, 0); }
+
+ void release() {
+ ptr_ = nullptr;
+ len_ = 0;
+ }
+
+ private:
+ void* ptr_ = nullptr;
+ size_t len_ = 0;
+};
+
+// Wrapper around mmap(2) that returns a Mapping.
+inline PosixErrorOr<Mapping> Mmap(void* addr, size_t length, int prot,
+ int flags, int fd, off_t offset) {
+ void* ptr = mmap(addr, length, prot, flags, fd, offset);
+ if (ptr == MAP_FAILED) {
+ return PosixError(
+ errno, absl::StrFormat("mmap(%p, %d, %x, %x, %d, %d)", addr, length,
+ prot, flags, fd, offset));
+ }
+ MaybeSave();
+ return Mapping(ptr, length);
+}
+
+// Convenience wrapper around Mmap for anonymous mappings.
+inline PosixErrorOr<Mapping> MmapAnon(size_t length, int prot, int flags) {
+ return Mmap(nullptr, length, prot, flags | MAP_ANONYMOUS, -1, 0);
+}
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_MEMORY_UTIL_H_
diff --git a/test/util/mount_util.h b/test/util/mount_util.h
new file mode 100644
index 000000000..468170646
--- /dev/null
+++ b/test/util/mount_util.h
@@ -0,0 +1,48 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_MOUNT_UTIL_H_
+#define GVISOR_TEST_UTIL_MOUNT_UTIL_H_
+
+#include <errno.h>
+#include <sys/mount.h>
+#include <functional>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/util/cleanup.h"
+#include "test/util/posix_error.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Mount mounts the filesystem, and unmounts when the returned reference is
+// destroyed.
+inline PosixErrorOr<Cleanup> Mount(const std::string &source, const std::string &target,
+ const std::string &fstype, uint64_t mountflags,
+ const std::string &data, uint64_t umountflags) {
+ if (mount(source.c_str(), target.c_str(), fstype.c_str(), mountflags,
+ data.c_str()) == -1) {
+ return PosixError(errno, "mount failed");
+ }
+ return Cleanup([target, umountflags]() {
+ EXPECT_THAT(umount2(target.c_str(), umountflags), SyscallSucceeds());
+ });
+}
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_MOUNT_UTIL_H_
diff --git a/test/util/multiprocess_util.cc b/test/util/multiprocess_util.cc
new file mode 100644
index 000000000..12637db8c
--- /dev/null
+++ b/test/util/multiprocess_util.cc
@@ -0,0 +1,139 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/multiprocess_util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "absl/strings/str_cat.h"
+#include "test/util/cleanup.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename,
+ const ExecveArray& argv,
+ const ExecveArray& envv,
+ const std::function<void()>& fn, pid_t* child,
+ int* execve_errno) {
+ int pfds[2];
+ int ret = pipe2(pfds, O_CLOEXEC);
+ if (ret < 0) {
+ return PosixError(errno, "pipe failed");
+ }
+ FileDescriptor rfd(pfds[0]);
+ FileDescriptor wfd(pfds[1]);
+
+ int parent_stdout = dup(STDOUT_FILENO);
+ if (parent_stdout < 0) {
+ return PosixError(errno, "dup stdout");
+ }
+ int parent_stderr = dup(STDERR_FILENO);
+ if (parent_stdout < 0) {
+ return PosixError(errno, "dup stderr");
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ return PosixError(errno, "fork failed");
+ } else if (pid == 0) {
+ // Child.
+ rfd.reset();
+ if (dup2(parent_stdout, STDOUT_FILENO) < 0) {
+ _exit(3);
+ }
+ if (dup2(parent_stderr, STDERR_FILENO) < 0) {
+ _exit(4);
+ }
+ close(parent_stdout);
+ close(parent_stderr);
+
+ // Clean ourself up in case the parent doesn't.
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL)) {
+ _exit(3);
+ }
+
+ if (fn) {
+ fn();
+ }
+
+ execve(filename.c_str(), argv.get(), envv.get());
+ int error = errno;
+ if (WriteFd(pfds[1], &error, sizeof(error)) != sizeof(error)) {
+ // We can't do much if the write fails, but we can at least exit with a
+ // different code.
+ _exit(2);
+ }
+ _exit(1);
+ }
+
+ // Parent.
+ if (child) {
+ *child = pid;
+ }
+
+ auto cleanup = Cleanup([pid] {
+ kill(pid, SIGKILL);
+ RetryEINTR(waitpid)(pid, nullptr, 0);
+ });
+
+ wfd.reset();
+
+ int read_errno;
+ ret = ReadFd(rfd.get(), &read_errno, sizeof(read_errno));
+ if (ret == 0) {
+ // Other end of the pipe closed, execve must have succeeded.
+ read_errno = 0;
+ } else if (ret < 0) {
+ return PosixError(errno, "read pipe failed");
+ } else if (ret != sizeof(read_errno)) {
+ return PosixError(EPIPE, absl::StrCat("pipe read wrong size ", ret));
+ }
+
+ if (execve_errno) {
+ *execve_errno = read_errno;
+ }
+
+ return std::move(cleanup);
+}
+
+PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn) {
+ pid_t pid = fork();
+ if (pid == 0) {
+ fn();
+ _exit(0);
+ }
+ MaybeSave();
+ if (pid < 0) {
+ return PosixError(errno, "fork failed");
+ }
+
+ int status;
+ if (waitpid(pid, &status, 0) < 0) {
+ return PosixError(errno, "waitpid failed");
+ }
+
+ return status;
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/multiprocess_util.h b/test/util/multiprocess_util.h
new file mode 100644
index 000000000..c09d6167f
--- /dev/null
+++ b/test/util/multiprocess_util.h
@@ -0,0 +1,113 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_
+#define GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "test/util/cleanup.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+// Immutable holder for a dynamically-sized array of pointers to mutable char,
+// terminated by a null pointer, as required for the argv and envp arguments to
+// execve(2).
+class ExecveArray {
+ public:
+ // Constructs an empty ExecveArray.
+ ExecveArray() = default;
+
+ // Constructs an ExecveArray by copying strings from the given range. T must
+ // be a range over ranges of char.
+ template <typename T>
+ explicit ExecveArray(T const& strs) : ExecveArray(strs.begin(), strs.end()) {}
+
+ // Constructs an ExecveArray by copying strings from [first, last). InputIt
+ // must be an input iterator over a range over char.
+ template <typename InputIt>
+ ExecveArray(InputIt first, InputIt last) {
+ std::vector<size_t> offsets;
+ auto output_it = std::back_inserter(str_);
+ for (InputIt it = first; it != last; ++it) {
+ offsets.push_back(str_.size());
+ auto const& s = *it;
+ std::copy(s.begin(), s.end(), output_it);
+ str_.push_back('\0');
+ }
+ ptrs_.reserve(offsets.size() + 1);
+ for (auto offset : offsets) {
+ ptrs_.push_back(str_.data() + offset);
+ }
+ ptrs_.push_back(nullptr);
+ }
+
+ // Constructs an ExecveArray by copying strings from list. This overload must
+ // exist independently of the single-argument template constructor because
+ // std::initializer_list does not participate in template argument deduction
+ // (i.e. cannot be type-inferred in an invocation of the templated
+ // constructor).
+ /* implicit */ ExecveArray(std::initializer_list<absl::string_view> list)
+ : ExecveArray(list.begin(), list.end()) {}
+
+ // Disable move construction and assignment since ptrs_ points into str_.
+ ExecveArray(ExecveArray&&) = delete;
+ ExecveArray& operator=(ExecveArray&&) = delete;
+
+ char* const* get() const { return ptrs_.data(); }
+
+ private:
+ std::vector<char> str_;
+ std::vector<char*> ptrs_;
+};
+
+// Simplified version of SubProcess. Returns OK and a cleanup function to kill
+// the child if it made it to execve.
+//
+// fn is run between fork and exec. If it needs to fail, it should exit the
+// process.
+//
+// The child pid is returned via child, if provided.
+// execve's error code is returned via execve_errno, if provided.
+PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename,
+ const ExecveArray& argv,
+ const ExecveArray& envv,
+ const std::function<void()>& fn, pid_t* child,
+ int* execve_errno);
+
+inline PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename,
+ const ExecveArray& argv,
+ const ExecveArray& envv, pid_t* child,
+ int* execve_errno) {
+ return ForkAndExec(filename, argv, envv, [] {}, child, execve_errno);
+}
+
+// Calls fn in a forked subprocess and returns the exit status of the
+// subprocess.
+//
+// fn must be async-signal-safe.
+PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn);
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_
diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc
new file mode 100644
index 000000000..9db72c6de
--- /dev/null
+++ b/test/util/posix_error.cc
@@ -0,0 +1,93 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/posix_error.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include "absl/strings/str_cat.h"
+
+namespace gvisor {
+namespace testing {
+
+std::string PosixError::ToString() const {
+ if (ok()) {
+ return "No Error";
+ }
+
+ std::string ret;
+
+ char strerrno_buf[1024] = {};
+ char* msg = nullptr;
+ if ((msg = strerror_r(errno_, strerrno_buf, sizeof(strerrno_buf))) ==
+ nullptr) {
+ ret = absl::StrCat("PosixError(errno=", errno_, " strerror_r FAILED)");
+ } else {
+ ret = absl::StrCat("PosixError(errno=", errno_, " ", msg, ")");
+ }
+
+ if (!msg_.empty()) {
+ ret.append(" ");
+ ret.append(msg_);
+ }
+
+ return ret;
+}
+
+::std::ostream& operator<<(::std::ostream& os, const PosixError& e) {
+ os << e.ToString();
+ return os;
+}
+
+void PosixErrorIsMatcherCommonImpl::DescribeTo(std::ostream* os) const {
+ *os << "has an errno value that ";
+ code_matcher_.DescribeTo(os);
+ *os << ", and has an error message that ";
+ message_matcher_.DescribeTo(os);
+}
+
+void PosixErrorIsMatcherCommonImpl::DescribeNegationTo(std::ostream* os) const {
+ *os << "has an errno value that ";
+ code_matcher_.DescribeNegationTo(os);
+ *os << ", or has an error message that ";
+ message_matcher_.DescribeNegationTo(os);
+}
+
+bool PosixErrorIsMatcherCommonImpl::MatchAndExplain(
+ const PosixError& error,
+ ::testing::MatchResultListener* result_listener) const {
+ ::testing::StringMatchResultListener inner_listener;
+
+ inner_listener.Clear();
+ if (!code_matcher_.MatchAndExplain(error.errno_value(), &inner_listener)) {
+ *result_listener << (inner_listener.str().empty()
+ ? "whose errno value is wrong"
+ : "which has a errno value " +
+ inner_listener.str());
+ return false;
+ }
+
+ if (!message_matcher_.Matches(error.error_message())) {
+ *result_listener << "whose error message is wrong";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/posix_error.h b/test/util/posix_error.h
new file mode 100644
index 000000000..8450be9b9
--- /dev/null
+++ b/test/util/posix_error.h
@@ -0,0 +1,428 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_POSIX_ERROR_H_
+#define GVISOR_TEST_UTIL_POSIX_ERROR_H_
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/variant.h"
+#include "test/util/logging.h"
+
+namespace gvisor {
+namespace testing {
+
+class PosixErrorIsMatcherCommonImpl;
+
+template <typename T>
+class PosixErrorOr;
+
+class ABSL_MUST_USE_RESULT PosixError {
+ public:
+ PosixError() {}
+ explicit PosixError(int errno_value) : errno_(errno_value) {}
+ PosixError(int errno_value, std::string msg)
+ : errno_(errno_value), msg_(std::move(msg)) {}
+
+ PosixError(PosixError&& other) = default;
+ PosixError& operator=(PosixError&& other) = default;
+ PosixError(const PosixError&) = default;
+ PosixError& operator=(const PosixError&) = default;
+
+ bool ok() const { return errno_ == 0; }
+
+ // Returns a reference to *this to make matchers compatible with
+ // PosixErrorOr.
+ const PosixError& error() const { return *this; }
+
+ std::string error_message() const { return msg_; }
+
+ // ToString produces a full std::string representation of this posix error
+ // including the printable representation of the errno and the error message.
+ std::string ToString() const;
+
+ // Ignores any errors. This method does nothing except potentially suppress
+ // complaints from any tools that are checking that errors are not dropped on
+ // the floor.
+ void IgnoreError() const {}
+
+ private:
+ int errno_value() const { return errno_; }
+ int errno_ = 0;
+ std::string msg_;
+
+ friend class PosixErrorIsMatcherCommonImpl;
+
+ template <typename T>
+ friend class PosixErrorOr;
+};
+
+template <typename T>
+class ABSL_MUST_USE_RESULT PosixErrorOr {
+ public:
+ PosixErrorOr(const PosixError& error); // NOLINT
+ explicit PosixErrorOr(const T& value);
+ PosixErrorOr(T&& value); // NOLINT
+
+ PosixErrorOr(PosixErrorOr&& other) = default;
+ PosixErrorOr& operator=(PosixErrorOr&& other) = default;
+ PosixErrorOr(const PosixErrorOr&) = default;
+ PosixErrorOr& operator=(const PosixErrorOr&) = default;
+
+ // Conversion copy/move constructor, T must be convertible from U.
+ template <typename U>
+ friend class PosixErrorOr;
+
+ template <typename U>
+ PosixErrorOr(PosixErrorOr<U> other);
+
+ template <typename U>
+ PosixErrorOr& operator=(PosixErrorOr<U> other);
+
+ // Return a reference to the error or NoError().
+ const PosixError error() const;
+
+ // Returns this->error().error_message();
+ const std::string error_message() const;
+
+ // Returns this->error().ok()
+ bool ok() const;
+
+ // Returns a reference to our current value, or CHECK-fails if !this->ok().
+ const T& ValueOrDie() const;
+ T& ValueOrDie();
+
+ // Ignores any errors. This method does nothing except potentially suppress
+ // complaints from any tools that are checking that errors are not dropped on
+ // the floor.
+ void IgnoreError() const {}
+
+ private:
+ const int errno_value() const;
+ absl::variant<T, PosixError> value_;
+
+ friend class PosixErrorIsMatcherCommonImpl;
+};
+
+template <typename T>
+PosixErrorOr<T>::PosixErrorOr(const PosixError& error) : value_(error) {}
+
+template <typename T>
+PosixErrorOr<T>::PosixErrorOr(const T& value) : value_(value) {}
+
+template <typename T>
+PosixErrorOr<T>::PosixErrorOr(T&& value) : value_(std::move(value)) {}
+
+// Conversion copy/move constructor, T must be convertible from U.
+template <typename T>
+template <typename U>
+inline PosixErrorOr<T>::PosixErrorOr(PosixErrorOr<U> other) {
+ if (absl::holds_alternative<U>(other.value_)) {
+ // T is convertible from U.
+ value_ = absl::get<U>(std::move(other.value_));
+ } else if (absl::holds_alternative<PosixError>(other.value_)) {
+ value_ = absl::get<PosixError>(std::move(other.value_));
+ } else {
+ TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value");
+ }
+}
+
+template <typename T>
+template <typename U>
+inline PosixErrorOr<T>& PosixErrorOr<T>::operator=(PosixErrorOr<U> other) {
+ if (absl::holds_alternative<U>(other.value_)) {
+ // T is convertible from U.
+ value_ = absl::get<U>(std::move(other.value_));
+ } else if (absl::holds_alternative<PosixError>(other.value_)) {
+ value_ = absl::get<PosixError>(std::move(other.value_));
+ } else {
+ TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value");
+ }
+ return *this;
+}
+
+template <typename T>
+const PosixError PosixErrorOr<T>::error() const {
+ if (!absl::holds_alternative<PosixError>(value_)) {
+ return PosixError();
+ }
+ return absl::get<PosixError>(value_);
+}
+
+template <typename T>
+const int PosixErrorOr<T>::errno_value() const {
+ return error().errno_value();
+}
+
+template <typename T>
+const std::string PosixErrorOr<T>::error_message() const {
+ return error().error_message();
+}
+
+template <typename T>
+bool PosixErrorOr<T>::ok() const {
+ return error().ok();
+}
+
+template <typename T>
+const T& PosixErrorOr<T>::ValueOrDie() const {
+ TEST_CHECK(absl::holds_alternative<T>(value_));
+ return absl::get<T>(value_);
+}
+
+template <typename T>
+T& PosixErrorOr<T>::ValueOrDie() {
+ TEST_CHECK(absl::holds_alternative<T>(value_));
+ return absl::get<T>(value_);
+}
+
+extern ::std::ostream& operator<<(::std::ostream& os, const PosixError& e);
+
+template <typename T>
+::std::ostream& operator<<(::std::ostream& os, const PosixErrorOr<T>& e) {
+ os << e.error();
+ return os;
+}
+
+// NoError is a PosixError that represents a successful state, i.e. No Error.
+inline PosixError NoError() { return PosixError(); }
+
+// Monomorphic implementation of matcher IsPosixErrorOk() for a given type T.
+// T can be PosixError, PosixErrorOr<>, or a reference to either of them.
+template <typename T>
+class MonoPosixErrorIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
+ public:
+ void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "is not OK";
+ }
+ bool MatchAndExplain(T actual_value,
+ ::testing::MatchResultListener*) const override {
+ return actual_value.ok();
+ }
+};
+
+// Implements IsPosixErrorOkMatcher() as a polymorphic matcher.
+class IsPosixErrorOkMatcher {
+ public:
+ template <typename T>
+ operator ::testing::Matcher<T>() const { // NOLINT
+ return MakeMatcher(new MonoPosixErrorIsOkMatcherImpl<T>());
+ }
+};
+
+// Monomorphic implementation of a matcher for a PosixErrorOr.
+template <typename PosixErrorOrType>
+class IsPosixErrorOkAndHoldsMatcherImpl
+ : public ::testing::MatcherInterface<PosixErrorOrType> {
+ public:
+ using ValueType = typename std::remove_reference<decltype(
+ std::declval<PosixErrorOrType>().ValueOrDie())>::type;
+
+ template <typename InnerMatcher>
+ explicit IsPosixErrorOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
+ : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>(
+ std::forward<InnerMatcher>(inner_matcher))) {}
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "is OK and has a value that ";
+ inner_matcher_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "isn't OK or has a value that ";
+ inner_matcher_.DescribeNegationTo(os);
+ }
+
+ bool MatchAndExplain(
+ PosixErrorOrType actual_value,
+ ::testing::MatchResultListener* listener) const override {
+ if (!actual_value.ok()) {
+ *listener << "which has error value " << actual_value.error();
+ return false;
+ }
+
+ ::testing::StringMatchResultListener inner_listener;
+ const bool matches = inner_matcher_.MatchAndExplain(
+ actual_value.ValueOrDie(), &inner_listener);
+ const std::string inner_explanation = inner_listener.str();
+ if (!inner_explanation.empty()) {
+ *listener << "which contains value "
+ << ::testing::PrintToString(actual_value.ValueOrDie()) << ", "
+ << inner_explanation;
+ }
+ return matches;
+ }
+
+ private:
+ const ::testing::Matcher<const ValueType&> inner_matcher_;
+};
+
+// Implements IsOkAndHolds() as a polymorphic matcher.
+template <typename InnerMatcher>
+class IsPosixErrorOkAndHoldsMatcher {
+ public:
+ explicit IsPosixErrorOkAndHoldsMatcher(InnerMatcher inner_matcher)
+ : inner_matcher_(std::move(inner_matcher)) {}
+
+ // Converts this polymorphic matcher to a monomorphic one of the given type.
+ // PosixErrorOrType can be either PosixErrorOr<T> or a reference to
+ // PosixErrorOr<T>.
+ template <typename PosixErrorOrType>
+ operator ::testing::Matcher<PosixErrorOrType>() const { // NOLINT
+ return ::testing::MakeMatcher(
+ new IsPosixErrorOkAndHoldsMatcherImpl<PosixErrorOrType>(
+ inner_matcher_));
+ }
+
+ private:
+ const InnerMatcher inner_matcher_;
+};
+
+// PosixErrorIs() is a polymorphic matcher. This class is the common
+// implementation of it shared by all types T where PosixErrorIs() can be
+// used as a Matcher<T>.
+class PosixErrorIsMatcherCommonImpl {
+ public:
+ PosixErrorIsMatcherCommonImpl(
+ ::testing::Matcher<int> code_matcher,
+ ::testing::Matcher<const std::string&> message_matcher)
+ : code_matcher_(std::move(code_matcher)),
+ message_matcher_(std::move(message_matcher)) {}
+
+ void DescribeTo(std::ostream* os) const;
+
+ void DescribeNegationTo(std::ostream* os) const;
+
+ bool MatchAndExplain(const PosixError& error,
+ ::testing::MatchResultListener* result_listener) const;
+
+ private:
+ const ::testing::Matcher<int> code_matcher_;
+ const ::testing::Matcher<const std::string&> message_matcher_;
+};
+
+// Monomorphic implementation of matcher PosixErrorIs() for a given type
+// T. T can be PosixError, PosixErrorOr<>, or a reference to either of them.
+template <typename T>
+class MonoPosixErrorIsMatcherImpl : public ::testing::MatcherInterface<T> {
+ public:
+ explicit MonoPosixErrorIsMatcherImpl(
+ PosixErrorIsMatcherCommonImpl common_impl)
+ : common_impl_(std::move(common_impl)) {}
+
+ void DescribeTo(std::ostream* os) const override {
+ common_impl_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ common_impl_.DescribeNegationTo(os);
+ }
+
+ bool MatchAndExplain(
+ T actual_value,
+ ::testing::MatchResultListener* result_listener) const override {
+ return common_impl_.MatchAndExplain(actual_value.error(), result_listener);
+ }
+
+ private:
+ PosixErrorIsMatcherCommonImpl common_impl_;
+};
+
+inline ::testing::Matcher<int> ToErrorCodeMatcher(
+ const ::testing::Matcher<int>& m) {
+ return m;
+}
+
+// Implements PosixErrorIs() as a polymorphic matcher.
+class PosixErrorIsMatcher {
+ public:
+ template <typename ErrorCodeMatcher>
+ PosixErrorIsMatcher(ErrorCodeMatcher&& code_matcher,
+ ::testing::Matcher<const std::string&> message_matcher)
+ : common_impl_(
+ ToErrorCodeMatcher(std::forward<ErrorCodeMatcher>(code_matcher)),
+ std::move(message_matcher)) {}
+
+ // Converts this polymorphic matcher to a monomorphic matcher of the
+ // given type. T can be StatusOr<>, Status, or a reference to
+ // either of them.
+ template <typename T>
+ operator ::testing::Matcher<T>() const { // NOLINT
+ return MakeMatcher(new MonoPosixErrorIsMatcherImpl<T>(common_impl_));
+ }
+
+ private:
+ const PosixErrorIsMatcherCommonImpl common_impl_;
+};
+
+// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose
+// whose error code matches code_matcher, and whose error message matches
+// message_matcher.
+template <typename ErrorCodeMatcher>
+PosixErrorIsMatcher PosixErrorIs(
+ ErrorCodeMatcher&& code_matcher,
+ ::testing::Matcher<const std::string&> message_matcher) {
+ return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher),
+ std::move(message_matcher));
+}
+
+// Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and
+// value matches the inner matcher.
+template <typename InnerMatcher>
+IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>
+IsPosixErrorOkAndHolds(InnerMatcher&& inner_matcher) {
+ return IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>(
+ std::forward<InnerMatcher>(inner_matcher));
+}
+
+// Internal helper for concatenating macro values.
+#define POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) x##y
+#define POSIX_ERROR_IMPL_CONCAT_(x, y) POSIX_ERROR_IMPL_CONCAT_INNER_(x, y)
+
+#define POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_(posixerroror, lhs, rexpr) \
+ auto posixerroror = (rexpr); \
+ if (!posixerroror.ok()) { \
+ return (posixerroror.error()); \
+ } \
+ lhs = std::move(posixerroror.ValueOrDie())
+
+#define EXPECT_NO_ERRNO(expression) \
+ EXPECT_THAT(expression, IsPosixErrorOkMatcher())
+#define ASSERT_NO_ERRNO(expression) \
+ ASSERT_THAT(expression, IsPosixErrorOkMatcher())
+
+#define ASSIGN_OR_RETURN_ERRNO(lhs, rexpr) \
+ POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_( \
+ POSIX_ERROR_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr)
+
+#define RETURN_IF_ERRNO(s) \
+ do { \
+ if (!s.ok()) return s; \
+ } while (false);
+
+#define ASSERT_NO_ERRNO_AND_VALUE(expr) \
+ ({ \
+ auto _expr_result = (expr); \
+ ASSERT_NO_ERRNO(_expr_result); \
+ std::move(_expr_result.ValueOrDie()); \
+ })
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_POSIX_ERROR_H_
diff --git a/test/util/posix_error_test.cc b/test/util/posix_error_test.cc
new file mode 100644
index 000000000..535b9f66a
--- /dev/null
+++ b/test/util/posix_error_test.cc
@@ -0,0 +1,45 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/posix_error.h"
+
+#include <errno.h>
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+TEST(PosixErrorTest, PosixError) {
+ auto err = PosixError(EAGAIN);
+ EXPECT_THAT(err, PosixErrorIs(EAGAIN, ""));
+}
+
+TEST(PosixErrorTest, PosixErrorOrPosixError) {
+ auto err = PosixErrorOr<std::nullptr_t>(PosixError(EAGAIN));
+ EXPECT_THAT(err, PosixErrorIs(EAGAIN, ""));
+}
+
+TEST(PosixErrorTest, PosixErrorOrNullptr) {
+ auto err = PosixErrorOr<std::nullptr_t>(nullptr);
+ EXPECT_THAT(err, PosixErrorIs(0, ""));
+ EXPECT_NO_ERRNO(err);
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/proc_util.cc b/test/util/proc_util.cc
new file mode 100644
index 000000000..72f7e67d0
--- /dev/null
+++ b/test/util/proc_util.cc
@@ -0,0 +1,98 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/proc_util.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "test/util/fs_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Parses a single line from /proc/<xxx>/maps.
+PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line) {
+ ProcMapsEntry map_entry = {};
+ std::vector<std::string> parts = absl::StrSplit(line, ' ', absl::SkipEmpty());
+
+ // A size of 5 means there is no file name specified.
+ if (parts.size() != 5 && parts.size() != 6) {
+ return PosixError(EINVAL, absl::StrCat("Invalid line: ", line));
+ }
+
+ // Address range in the form X-X where X are hex values without leading 0x.
+ std::vector<std::string> addresses = absl::StrSplit(parts[0], '-');
+ if (addresses.size() != 2) {
+ return PosixError(EINVAL,
+ absl::StrCat("Invalid address range: ", parts[0]));
+ }
+ ASSIGN_OR_RETURN_ERRNO(map_entry.start, AtoiBase(addresses[0], 16));
+ ASSIGN_OR_RETURN_ERRNO(map_entry.end, AtoiBase(addresses[1], 16));
+
+ // Permissions are four bytes of the form rwxp or - if permission not set.
+ if (parts[1].size() != 4) {
+ return PosixError(EINVAL,
+ absl::StrCat("Invalid permission field: ", parts[1]));
+ }
+
+ map_entry.readable = parts[1][0] == 'r';
+ map_entry.writable = parts[1][1] == 'w';
+ map_entry.executable = parts[1][2] == 'x';
+ map_entry.priv = parts[1][3] == 'p';
+
+ ASSIGN_OR_RETURN_ERRNO(map_entry.offset, AtoiBase(parts[2], 16));
+
+ std::vector<std::string> device = absl::StrSplit(parts[3], ':');
+ if (device.size() != 2) {
+ return PosixError(EINVAL, absl::StrCat("Invalid device: ", parts[3]));
+ }
+ ASSIGN_OR_RETURN_ERRNO(map_entry.major, AtoiBase(device[0], 16));
+ ASSIGN_OR_RETURN_ERRNO(map_entry.minor, AtoiBase(device[1], 16));
+
+ ASSIGN_OR_RETURN_ERRNO(map_entry.inode, Atoi<int64_t>(parts[4]));
+ if (parts.size() == 6) {
+ // A filename is present.
+ map_entry.filename = parts[5];
+ }
+
+ return map_entry;
+}
+
+PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps(
+ absl::string_view contents) {
+ std::vector<ProcMapsEntry> entries;
+ auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty());
+ for (const auto& l : lines) {
+ LOG(INFO) << "line: " << l;
+ ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcMapsLine(l));
+ entries.push_back(entry);
+ }
+ return entries;
+}
+
+PosixErrorOr<bool> IsVsyscallEnabled() {
+ ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps"));
+ ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents));
+ return std::any_of(maps.begin(), maps.end(), [](const ProcMapsEntry& e) {
+ return e.filename == "[vsyscall]";
+ });
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/proc_util.h b/test/util/proc_util.h
new file mode 100644
index 000000000..f8021d92e
--- /dev/null
+++ b/test/util/proc_util.h
@@ -0,0 +1,150 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_PROC_UTIL_H_
+#define GVISOR_TEST_UTIL_PROC_UTIL_H_
+
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "test/util/fs_util.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+// ProcMapsEntry contains the data from a single line in /proc/<xxx>/maps.
+struct ProcMapsEntry {
+ uint64_t start;
+ uint64_t end;
+ bool readable;
+ bool writable;
+ bool executable;
+ bool priv;
+ uint64_t offset;
+ int major;
+ int minor;
+ int64_t inode;
+ std::string filename;
+};
+
+// Parses a ProcMaps line or returns an error.
+PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line);
+PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps(
+ absl::string_view contents);
+
+// Returns true if vsyscall (emmulation or not) is enabled.
+PosixErrorOr<bool> IsVsyscallEnabled();
+
+// Printer for ProcMapsEntry.
+inline std::ostream& operator<<(std::ostream& os, const ProcMapsEntry& entry) {
+ std::string str =
+ absl::StrCat(absl::Hex(entry.start, absl::PadSpec::kZeroPad8), "-",
+ absl::Hex(entry.end, absl::PadSpec::kZeroPad8), " ");
+
+ absl::StrAppend(&str, entry.readable ? "r" : "-");
+ absl::StrAppend(&str, entry.writable ? "w" : "-");
+ absl::StrAppend(&str, entry.executable ? "x" : "-");
+ absl::StrAppend(&str, entry.priv ? "p" : "s");
+
+ absl::StrAppend(&str, " ", absl::Hex(entry.offset, absl::PadSpec::kZeroPad8),
+ " ", absl::Hex(entry.major, absl::PadSpec::kZeroPad2), ":",
+ absl::Hex(entry.minor, absl::PadSpec::kZeroPad2), " ",
+ entry.inode);
+ if (absl::string_view(entry.filename) != "") {
+ // Pad to column 74
+ int pad = 73 - str.length();
+ if (pad > 0) {
+ absl::StrAppend(&str, std::string(pad, ' '));
+ }
+ absl::StrAppend(&str, entry.filename);
+ }
+ os << str;
+ return os;
+}
+
+// Printer for std::vector<ProcMapsEntry>.
+inline std::ostream& operator<<(std::ostream& os,
+ const std::vector<ProcMapsEntry>& vec) {
+ for (unsigned int i = 0; i < vec.size(); i++) {
+ os << vec[i];
+ if (i != vec.size() - 1) {
+ os << "\n";
+ }
+ }
+ return os;
+}
+
+// GMock printer for std::vector<ProcMapsEntry>.
+inline void PrintTo(const std::vector<ProcMapsEntry>& vec, std::ostream* os) {
+ *os << vec;
+}
+
+// Checks that /proc/pid/maps contains all of the passed mappings.
+//
+// The major, minor, and inode fields are ignored.
+MATCHER_P(ContainsMappings, mappings,
+ "contains mappings:\n" + ::testing::PrintToString(mappings)) {
+ auto contents_or = GetContents(absl::StrCat("/proc/", arg, "/maps"));
+ if (!contents_or.ok()) {
+ *result_listener << "Unable to read mappings: "
+ << contents_or.error().ToString();
+ return false;
+ }
+
+ auto maps_or = ParseProcMaps(contents_or.ValueOrDie());
+ if (!maps_or.ok()) {
+ *result_listener << "Unable to parse mappings: "
+ << maps_or.error().ToString();
+ return false;
+ }
+
+ auto maps = std::move(maps_or.ValueOrDie());
+
+ // Does maps contain all elements in mappings? The comparator ignores
+ // the major, minor, and inode fields.
+ bool all_present = true;
+ std::for_each(mappings.begin(), mappings.end(), [&](const ProcMapsEntry& e1) {
+ auto it =
+ std::find_if(maps.begin(), maps.end(), [&e1](const ProcMapsEntry& e2) {
+ return e1.start == e2.start && e1.end == e2.end &&
+ e1.readable == e2.readable && e1.writable == e2.writable &&
+ e1.executable == e2.executable && e1.priv == e2.priv &&
+ e1.offset == e2.offset && e1.filename == e2.filename;
+ });
+ if (it == maps.end()) {
+ // It wasn't found.
+ if (all_present) {
+ // We will output the message once and then a line for each mapping
+ // that wasn't found.
+ all_present = false;
+ *result_listener << "Got mappings:\n"
+ << maps << "\nThat were missing:\n";
+ }
+ *result_listener << e1 << "\n";
+ }
+ });
+
+ return all_present;
+}
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_PROC_UTIL_H_
diff --git a/test/util/save_util.cc b/test/util/save_util.cc
new file mode 100644
index 000000000..71f4078a7
--- /dev/null
+++ b/test/util/save_util.cc
@@ -0,0 +1,59 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/save_util.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <atomic>
+#include <cerrno>
+
+#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST"
+
+namespace gvisor {
+namespace testing {
+namespace {
+
+bool CooperativeSaveEnabled() {
+ static bool enabled = getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr;
+ return enabled;
+}
+
+std::atomic<int> save_disable;
+
+} // namespace
+
+DisableSave::DisableSave() { save_disable++; }
+
+DisableSave::~DisableSave() { reset(); }
+
+void DisableSave::reset() {
+ if (!reset_) {
+ reset_ = true;
+ save_disable--;
+ }
+}
+
+void MaybeSave() {
+ if (CooperativeSaveEnabled() && !save_disable.load()) {
+ int orig_errno = errno;
+ syscall(SYS_create_module, nullptr, 0);
+ errno = orig_errno;
+ }
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/save_util.h b/test/util/save_util.h
new file mode 100644
index 000000000..919e4af3d
--- /dev/null
+++ b/test/util/save_util.h
@@ -0,0 +1,47 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_SAVE_UTIL_H_
+#define GVISOR_TEST_UTIL_SAVE_UTIL_H_
+
+namespace gvisor {
+namespace testing {
+// Disable save prevents saving while the given function executes.
+//
+// This lasts the duration of the object, unless reset is called.
+class DisableSave {
+ public:
+ DisableSave();
+ ~DisableSave();
+ DisableSave(DisableSave const&) = delete;
+ DisableSave(DisableSave&&) = delete;
+ DisableSave& operator=(DisableSave const&) = delete;
+ DisableSave& operator=(DisableSave&&) = delete;
+
+ // reset allows saves to continue, and is called implicitly by the destructor.
+ // It may be called multiple times safely, but is not thread-safe.
+ void reset();
+
+ private:
+ bool reset_ = false;
+};
+
+// May perform a co-operative save cycle.
+//
+// errno is guaranteed to be preserved.
+void MaybeSave();
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_SAVE_UTIL_H_
diff --git a/test/util/signal_util.cc b/test/util/signal_util.cc
new file mode 100644
index 000000000..3e2df32a6
--- /dev/null
+++ b/test/util/signal_util.cc
@@ -0,0 +1,103 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/signal_util.h"
+
+#include <signal.h>
+#include <ostream>
+
+#include "gtest/gtest.h"
+#include "test/util/cleanup.h"
+#include "test/util/posix_error.h"
+#include "test/util/test_util.h"
+
+namespace {
+
+struct Range {
+ int start;
+ int end;
+};
+
+// Format a Range as "start-end" or "start" for single value Ranges.
+static ::std::ostream& operator<<(::std::ostream& os, const Range& range) {
+ if (range.end > range.start) {
+ return os << range.start << '-' << range.end;
+ }
+
+ return os << range.start;
+}
+
+} // namespace
+
+// Format a sigset_t as a comma separated list of numeric ranges.
+// Empty sigset: []
+// Full sigset: [1-31,34-64]
+::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset) {
+ const char* delim = "";
+ Range range = {0, 0};
+
+ os << '[';
+
+ for (int sig = 1; sig <= gvisor::testing::kMaxSignal; ++sig) {
+ if (sigismember(&sigset, sig)) {
+ if (range.start) {
+ range.end = sig;
+ } else {
+ range.start = sig;
+ range.end = sig;
+ }
+ } else if (range.start) {
+ os << delim << range;
+ delim = ",";
+ range.start = 0;
+ range.end = 0;
+ }
+ }
+
+ if (range.start) {
+ os << delim << range;
+ }
+
+ return os << ']';
+}
+
+namespace gvisor {
+namespace testing {
+
+PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa) {
+ struct sigaction old_sa;
+ int rc = sigaction(sig, &sa, &old_sa);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "sigaction failed");
+ }
+ return Cleanup([sig, old_sa] {
+ EXPECT_THAT(sigaction(sig, &old_sa, nullptr), SyscallSucceeds());
+ });
+}
+
+PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set) {
+ sigset_t old;
+ int rc = sigprocmask(how, &set, &old);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "sigprocmask failed");
+ }
+ return Cleanup([old] {
+ EXPECT_THAT(sigprocmask(SIG_SETMASK, &old, nullptr), SyscallSucceeds());
+ });
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/signal_util.h b/test/util/signal_util.h
new file mode 100644
index 000000000..f58f4c6c4
--- /dev/null
+++ b/test/util/signal_util.h
@@ -0,0 +1,92 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_SIGNAL_UTIL_H_
+#define GVISOR_TEST_UTIL_SIGNAL_UTIL_H_
+
+#include <signal.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <ostream>
+
+#include "gmock/gmock.h"
+#include "test/util/cleanup.h"
+#include "test/util/posix_error.h"
+
+// Format a sigset_t as a comma separated list of numeric ranges.
+::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset);
+
+namespace gvisor {
+namespace testing {
+
+// The maximum signal number.
+static constexpr int kMaxSignal = 64;
+
+// Wrapper for the tgkill(2) syscall, which glibc does not provide.
+inline int tgkill(pid_t tgid, pid_t tid, int sig) {
+ return syscall(__NR_tgkill, tgid, tid, sig);
+}
+
+// Installs the passed sigaction and returns a cleanup function to restore the
+// previous handler when it goes out of scope.
+PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa);
+
+// Updates the signal mask as per sigprocmask(2) and returns a cleanup function
+// to restore the previous signal mask when it goes out of scope.
+PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set);
+
+// ScopedSignalMask variant that creates a mask of the single signal 'sig'.
+inline PosixErrorOr<Cleanup> ScopedSignalMask(int how, int sig) {
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, sig);
+ return ScopedSignalMask(how, set);
+}
+
+// Asserts equality of two sigset_t values.
+MATCHER_P(EqualsSigset, value, "equals " + ::testing::PrintToString(value)) {
+ for (int sig = 1; sig <= kMaxSignal; ++sig) {
+ if (sigismember(&arg, sig) != sigismember(&value, sig)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#ifdef __x86_64__
+// Fault can be used to generate a synchronous SIGSEGV.
+//
+// This fault can be fixed up in a handler via fixup, below.
+inline void Fault() {
+ // Zero and dereference %ax.
+ asm("movabs $0, %%rax\r\n"
+ "mov 0(%%rax), %%rax\r\n"
+ :
+ :
+ : "ax");
+}
+
+// FixupFault fixes up a fault generated by fault, above.
+inline void FixupFault(ucontext* ctx) {
+ // Skip the bad instruction above.
+ //
+ // The encoding is 0x48 0xab 0x00.
+ ctx->uc_mcontext.gregs[REG_RIP] += 3;
+}
+#endif
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_SIGNAL_UTIL_H_
diff --git a/test/util/temp_path.cc b/test/util/temp_path.cc
new file mode 100644
index 000000000..e45909655
--- /dev/null
+++ b/test/util/temp_path.cc
@@ -0,0 +1,157 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/temp_path.h"
+
+#include <unistd.h>
+#include <atomic>
+#include <cstdlib>
+
+#include "gtest/gtest.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "test/util/fs_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+std::atomic<uint64_t> global_temp_file_number = ATOMIC_VAR_INIT(1);
+
+// Return a new temp filename, intended to be unique system-wide.
+//
+// The global file number helps maintain file naming consistency across
+// different runs of a test.
+//
+// The timestamp is necessary because the test infrastructure invokes each
+// test case in a separate process (resetting global_temp_file_number) and
+// potentially in parallel, which allows for races between selecting and using a
+// name.
+std::string NextTempBasename() {
+ return absl::StrCat("gvisor_test_temp_", global_temp_file_number++, "_",
+ absl::ToUnixNanos(absl::Now()));
+}
+
+void TryDeleteRecursively(std::string const& path) {
+ if (!path.empty()) {
+ int undeleted_dirs = 0;
+ int undeleted_files = 0;
+ auto status = RecursivelyDelete(path, &undeleted_dirs, &undeleted_files);
+ if (undeleted_dirs || undeleted_files || !status.ok()) {
+ LOG(WARNING) << path << ": failed to delete " << undeleted_dirs
+ << " directories and " << undeleted_files
+ << " files: " << status;
+ }
+ }
+}
+
+} // namespace
+
+constexpr mode_t TempPath::kDefaultFileMode;
+constexpr mode_t TempPath::kDefaultDirMode;
+
+std::string NewTempAbsPathInDir(absl::string_view const dir) {
+ return JoinPath(dir, NextTempBasename());
+}
+
+std::string NewTempAbsPath() { return NewTempAbsPathInDir(GetAbsoluteTestTmpdir()); }
+
+std::string NewTempRelPath() { return NextTempBasename(); }
+
+std::string GetAbsoluteTestTmpdir() {
+ char* env_tmpdir = getenv("TEST_TMPDIR");
+ std::string tmp_dir = env_tmpdir != nullptr ? std::string(env_tmpdir) : "/tmp";
+ return MakeAbsolute(tmp_dir, "").ValueOrDie();
+}
+
+PosixErrorOr<TempPath> TempPath::CreateFileWith(absl::string_view const parent,
+ absl::string_view const content,
+ mode_t const mode) {
+ return CreateIn(parent, [=](absl::string_view path) -> PosixError {
+ // SetContents will call open(O_WRONLY) with the given mode. If the
+ // mode is not user-writable, save/restore cannot preserve the fd. Hence
+ // the little permission dance that's done here.
+ auto res = CreateWithContents(path, content, mode | 0200);
+ RETURN_IF_ERRNO(res);
+
+ return Chmod(path, mode);
+ });
+}
+
+PosixErrorOr<TempPath> TempPath::CreateDirWith(absl::string_view const parent,
+ mode_t const mode) {
+ return CreateIn(parent,
+ [=](absl::string_view path) { return Mkdir(path, mode); });
+}
+
+PosixErrorOr<TempPath> TempPath::CreateSymlinkTo(absl::string_view const parent,
+ std::string const& dest) {
+ return CreateIn(parent, [=](absl::string_view path) {
+ int ret = symlink(dest.c_str(), std::string(path).c_str());
+ if (ret != 0) {
+ return PosixError(errno, "symlink failed");
+ }
+ return NoError();
+ });
+}
+
+PosixErrorOr<TempPath> TempPath::CreateFileIn(absl::string_view const parent) {
+ return TempPath::CreateFileWith(parent, absl::string_view(),
+ kDefaultFileMode);
+}
+
+PosixErrorOr<TempPath> TempPath::CreateDirIn(absl::string_view const parent) {
+ return TempPath::CreateDirWith(parent, kDefaultDirMode);
+}
+
+PosixErrorOr<TempPath> TempPath::CreateFileMode(mode_t mode) {
+ return TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), absl::string_view(),
+ mode);
+}
+
+PosixErrorOr<TempPath> TempPath::CreateFile() {
+ return TempPath::CreateFileIn(GetAbsoluteTestTmpdir());
+}
+
+PosixErrorOr<TempPath> TempPath::CreateDir() {
+ return TempPath::CreateDirIn(GetAbsoluteTestTmpdir());
+}
+
+TempPath::~TempPath() { TryDeleteRecursively(path_); }
+
+TempPath::TempPath(TempPath&& orig) { reset(orig.release()); }
+
+TempPath& TempPath::operator=(TempPath&& orig) {
+ reset(orig.release());
+ return *this;
+}
+
+std::string TempPath::reset(std::string newpath) {
+ std::string path = path_;
+ TryDeleteRecursively(path_);
+ path_ = std::move(newpath);
+ return path;
+}
+
+std::string TempPath::release() {
+ std::string path = path_;
+ path_ = std::string();
+ return path;
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/temp_path.h b/test/util/temp_path.h
new file mode 100644
index 000000000..33eb6a72c
--- /dev/null
+++ b/test/util/temp_path.h
@@ -0,0 +1,134 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_TEMP_PATH_H_
+#define GVISOR_TEST_UTIL_TEMP_PATH_H_
+
+#include <sys/stat.h>
+#include <string>
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+// Returns an absolute path for a file in `dir` that does not yet exist.
+// Distinct calls to NewTempAbsPathInDir from the same process, even from
+// multiple threads, are guaranteed to return different paths. Distinct calls to
+// NewTempAbsPathInDir from different processes are not synchronized.
+std::string NewTempAbsPathInDir(absl::string_view base);
+
+// Like NewTempAbsPathInDir, but the returned path is in the test's temporary
+// directory, as provided by the testing framework.
+std::string NewTempAbsPath();
+
+// Like NewTempAbsPathInDir, but the returned path is relative (to the current
+// working directory).
+std::string NewTempRelPath();
+
+// Returns the absolute path for the test temp dir.
+std::string GetAbsoluteTestTmpdir();
+
+// Represents a temporary file or directory.
+class TempPath {
+ public:
+ // Default creation mode for files.
+ static constexpr mode_t kDefaultFileMode = 0644;
+
+ // Default creation mode for directories.
+ static constexpr mode_t kDefaultDirMode = 0755;
+
+ // Creates a temporary file in directory `parent` with mode `mode` and
+ // contents `content`.
+ static PosixErrorOr<TempPath> CreateFileWith(absl::string_view parent,
+ absl::string_view content,
+ mode_t mode);
+
+ // Creates an empty temporary subdirectory in directory `parent` with mode
+ // `mode`.
+ static PosixErrorOr<TempPath> CreateDirWith(absl::string_view parent,
+ mode_t mode);
+
+ // Creates a temporary symlink in directory `parent` to destination `dest`.
+ static PosixErrorOr<TempPath> CreateSymlinkTo(absl::string_view parent,
+ std::string const& dest);
+
+ // Creates an empty temporary file in directory `parent` with mode
+ // kDefaultFileMode.
+ static PosixErrorOr<TempPath> CreateFileIn(absl::string_view parent);
+
+ // Creates an empty temporary subdirectory in directory `parent` with mode
+ // kDefaultDirMode.
+ static PosixErrorOr<TempPath> CreateDirIn(absl::string_view parent);
+
+ // Creates an empty temporary file in the test's temporary directory with mode
+ // `mode`.
+ static PosixErrorOr<TempPath> CreateFileMode(mode_t mode);
+
+ // Creates an empty temporary file in the test's temporary directory with
+ // mode kDefaultFileMode.
+ static PosixErrorOr<TempPath> CreateFile();
+
+ // Creates an empty temporary subdirectory in the test's temporary directory
+ // with mode kDefaultDirMode.
+ static PosixErrorOr<TempPath> CreateDir();
+
+ // Constructs a TempPath that represents nothing.
+ TempPath() = default;
+
+ // Constructs a TempPath that represents the given path, which will be deleted
+ // when the TempPath is destroyed.
+ explicit TempPath(std::string path) : path_(std::move(path)) {}
+
+ // Attempts to delete the represented temporary file or directory (in the
+ // latter case, also attempts to delete its contents).
+ ~TempPath();
+
+ // Attempts to delete the represented temporary file or directory, then
+ // transfers ownership of the path represented by orig to this TempPath.
+ TempPath(TempPath&& orig);
+ TempPath& operator=(TempPath&& orig);
+
+ // Changes the path this TempPath represents. If the TempPath already
+ // represented a path, deletes and returns that path. Otherwise returns the
+ // empty std::string.
+ std::string reset(std::string newpath);
+ std::string reset() { return reset(""); }
+
+ // Forgets and returns the path this TempPath represents. The path is not
+ // deleted.
+ std::string release();
+
+ // Returns the path this TempPath represents.
+ std::string path() const { return path_; }
+
+ private:
+ template <typename F>
+ static PosixErrorOr<TempPath> CreateIn(absl::string_view const parent,
+ F const& f) {
+ std::string path = NewTempAbsPathInDir(parent);
+ RETURN_IF_ERRNO(f(path));
+ return TempPath(std::move(path));
+ }
+
+ std::string path_;
+};
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_TEMP_PATH_H_
diff --git a/test/util/test_main.cc b/test/util/test_main.cc
new file mode 100644
index 000000000..4c6b5e860
--- /dev/null
+++ b/test/util/test_main.cc
@@ -0,0 +1,20 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/test_util.h"
+
+int main(int argc, char** argv) {
+ gvisor::testing::TestInit(&argc, &argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/test/util/test_util.cc b/test/util/test_util.cc
new file mode 100644
index 000000000..7b40260d1
--- /dev/null
+++ b/test/util/test_util.cc
@@ -0,0 +1,248 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/test_util.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include <ctime>
+#include <vector>
+#include "absl/base/attributes.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_split.h"
+#include "absl/time/time.h"
+#include "test/util/fs_util.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+#define TEST_ON_GVISOR "TEST_ON_GVISOR"
+
+bool IsRunningOnGvisor() { return GvisorPlatform() != Platform::kNative; }
+
+Platform GvisorPlatform() {
+ // Set by runner.go.
+ char* env = getenv(TEST_ON_GVISOR);
+ if (!env) {
+ return Platform::kNative;
+ }
+ if (strcmp(env, "ptrace") == 0) {
+ return Platform::kPtrace;
+ }
+ if (strcmp(env, "kvm") == 0) {
+ return Platform::kKVM;
+ }
+ LOG(FATAL) << "unknown platform " << env;
+ __builtin_unreachable();
+}
+
+// Inline cpuid instruction. Preserve %ebx/%rbx register. In PIC compilations
+// %ebx contains the address of the global offset table. %rbx is occasionally
+// used to address stack variables in presence of dynamic allocas.
+#if defined(__x86_64__)
+#define GETCPUID(a, b, c, d, a_inp, c_inp) \
+ asm("mov %%rbx, %%rdi\n" \
+ "cpuid\n" \
+ "xchg %%rdi, %%rbx\n" \
+ : "=a"(a), "=D"(b), "=c"(c), "=d"(d) \
+ : "a"(a_inp), "2"(c_inp))
+#endif // defined(__x86_64__)
+
+CPUVendor GetCPUVendor() {
+ uint32_t eax, ebx, ecx, edx;
+ std::string vendor_str;
+ // Get vendor std::string (issue CPUID with eax = 0)
+ GETCPUID(eax, ebx, ecx, edx, 0, 0);
+ vendor_str.append(reinterpret_cast<char*>(&ebx), 4);
+ vendor_str.append(reinterpret_cast<char*>(&edx), 4);
+ vendor_str.append(reinterpret_cast<char*>(&ecx), 4);
+ if (vendor_str == "GenuineIntel") {
+ return CPUVendor::kIntel;
+ } else if (vendor_str == "AuthenticAMD") {
+ return CPUVendor::kAMD;
+ }
+ return CPUVendor::kUnknownVendor;
+}
+
+bool operator==(const KernelVersion& first, const KernelVersion& second) {
+ return first.major == second.major && first.minor == second.minor &&
+ first.micro == second.micro;
+}
+
+PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_str) {
+ KernelVersion version = {};
+ std::vector<std::string> values = absl::StrSplit(vers_str, absl::ByAnyChar(".-"));
+ if (values.size() == 2) {
+ ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0]));
+ ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1]));
+ return version;
+ } else if (values.size() >= 3) {
+ ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0]));
+ ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1]));
+ ASSIGN_OR_RETURN_ERRNO(version.micro, Atoi<int>(values[2]));
+ return version;
+ }
+ return PosixError(EINVAL, absl::StrCat("Unknown kernel release: ", vers_str));
+}
+
+PosixErrorOr<KernelVersion> GetKernelVersion() {
+ utsname buf;
+ RETURN_ERROR_IF_SYSCALL_FAIL(uname(&buf));
+ return ParseKernelVersion(buf.release);
+}
+
+void SetupGvisorDeathTest() {
+}
+
+std::string CPUSetToString(const cpu_set_t& set, size_t cpus) {
+ std::string str = "cpuset[";
+ for (unsigned int n = 0; n < cpus; n++) {
+ if (CPU_ISSET(n, &set)) {
+ if (n != 0) {
+ absl::StrAppend(&str, " ");
+ }
+ absl::StrAppend(&str, n);
+ }
+ }
+ absl::StrAppend(&str, "]");
+ return str;
+}
+
+// An overloaded operator<< makes it easy to dump the value of an OpenFd.
+std::ostream& operator<<(std::ostream& out, OpenFd const& ofd) {
+ out << ofd.fd << " -> " << ofd.link;
+ return out;
+}
+
+// An overloaded operator<< makes it easy to dump a vector of OpenFDs.
+std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v) {
+ for (const auto& ofd : v) {
+ out << ofd << std::endl;
+ }
+ return out;
+}
+
+PosixErrorOr<std::vector<OpenFd>> GetOpenFDs() {
+ // Get the results from /proc/self/fd.
+ ASSIGN_OR_RETURN_ERRNO(auto dir_list,
+ ListDir("/proc/self/fd", /*skipdots=*/true));
+
+ std::vector<OpenFd> ret_fds;
+ for (const auto& str_fd : dir_list) {
+ OpenFd open_fd = {};
+ ASSIGN_OR_RETURN_ERRNO(open_fd.fd, Atoi<int>(str_fd));
+ std::string path = absl::StrCat("/proc/self/fd/", open_fd.fd);
+
+ // Resolve the link.
+ char buf[PATH_MAX] = {};
+ int ret = readlink(path.c_str(), buf, sizeof(buf));
+ if (ret < 0) {
+ if (errno == ENOENT) {
+ // The FD may have been closed, let's be resilient.
+ continue;
+ }
+
+ return PosixError(
+ errno, absl::StrCat("readlink of ", path, " returned errno ", errno));
+ }
+ open_fd.link = std::string(buf, ret);
+ ret_fds.emplace_back(std::move(open_fd));
+ }
+ return ret_fds;
+}
+
+PosixErrorOr<uint64_t> Links(const std::string& path) {
+ struct stat st;
+ if (stat(path.c_str(), &st)) {
+ return PosixError(errno, absl::StrCat("Failed to stat ", path));
+ }
+ return static_cast<uint64_t>(st.st_nlink);
+}
+
+void RandomizeBuffer(void* buffer, size_t len) {
+ struct timespec ts = {};
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ uint32_t seed = static_cast<uint32_t>(ts.tv_nsec);
+ char* const buf = static_cast<char*>(buffer);
+ for (size_t i = 0; i < len; i++) {
+ buf[i] = rand_r(&seed) % 255;
+ }
+}
+
+std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size,
+ void* buf,
+ size_t buflen) {
+ std::vector<std::vector<struct iovec>> result;
+ for (uint64_t offset = 0; offset < total_size;) {
+ auto& iovec_array = *result.emplace(result.end());
+
+ for (; offset < total_size && iovec_array.size() < IOV_MAX;
+ offset += buflen) {
+ struct iovec iov = {};
+ iov.iov_base = buf;
+ iov.iov_len = std::min<uint64_t>(total_size - offset, buflen);
+ iovec_array.push_back(iov);
+ }
+ }
+
+ return result;
+}
+
+void SleepSafe(absl::Duration duration) {
+ if (duration == absl::ZeroDuration()) {
+ return;
+ }
+
+ struct timespec ts = absl::ToTimespec(duration);
+ int ret;
+ while (1) {
+ ret = syscall(__NR_nanosleep, &ts, &ts);
+ if (ret == 0 || (ret <= 0 && errno != EINTR)) {
+ break;
+ }
+ }
+}
+
+uint64_t Megabytes(uint64_t n) {
+ // Overflow check, upper 20 bits in n shouldn't be set.
+ CHECK(!(0xfffff00000000000 & n));
+ return n << 20;
+}
+
+bool Equivalent(uint64_t current, uint64_t target, double tolerance) {
+ auto abs_diff = target > current ? target - current : current - target;
+ return abs_diff <= static_cast<uint64_t>(tolerance * target);
+}
+void TestInit(int* argc, char*** argv) {
+ ::testing::InitGoogleTest(argc, *argv);
+ ::gflags::ParseCommandLineFlags(argc, argv, true);
+
+ // Always mask SIGPIPE as it's common and tests aren't expected to handle it.
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_IGN;
+ TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0);
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/test_util.h b/test/util/test_util.h
new file mode 100644
index 000000000..2a7609e5c
--- /dev/null
+++ b/test/util/test_util.h
@@ -0,0 +1,794 @@
+// Copyright 2018 Google LLC
+//
+// 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.
+
+// Utilities for syscall testing.
+//
+// Initialization
+// ==============
+//
+// Prior to calling RUN_ALL_TESTS, all tests must use TestInit(&argc, &argv).
+// See the TestInit function for exact side-effects and semantics.
+//
+// Configuration
+// =============
+//
+// IsRunningOnGvisor returns true if the test is known to be running on gVisor.
+// GvisorPlatform can be used to get more detail:
+//
+// switch (GvisorPlatform()) {
+// case Platform::kNative:
+// case Platform::kGvisor:
+// EXPECT_THAT(mmap(...), SyscallSucceeds());
+// break;
+// case Platform::kPtrace:
+// EXPECT_THAT(mmap(...), SyscallFailsWithErrno(ENOSYS));
+// break;
+// }
+//
+// Matchers
+// ========
+//
+// ElementOf(xs) matches if the matched value is equal to an element of the
+// container xs. Example:
+//
+// // PASS
+// EXPECT_THAT(1, ElementOf({0, 1, 2}));
+//
+// // FAIL
+// // Value of: 3
+// // Expected: one of {0, 1, 2}
+// // Actual: 3
+// EXPECT_THAT(3, ElementOf({0, 1, 2}));
+//
+// SyscallSucceeds() matches if the syscall is successful. A successful syscall
+// is defined by either a return value not equal to -1, or a return value of -1
+// with an errno of 0 (which is a possible successful return for e.g.
+// PTRACE_PEEK). Example:
+//
+// // PASS
+// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallSucceeds());
+//
+// // FAIL
+// // Value of: open("/", O_RDWR)
+// // Expected: not -1 (success)
+// // Actual: -1 (of type int), with errno 21 (Is a directory)
+// EXPECT_THAT(open("/", O_RDWR), SyscallSucceeds());
+//
+// SyscallSucceedsWithValue(m) matches if the syscall is successful, and the
+// value also matches m. Example:
+//
+// // PASS
+// EXPECT_THAT(read(4, buf, 8192), SyscallSucceedsWithValue(8192));
+//
+// // FAIL
+// // Value of: read(-1, buf, 8192)
+// // Expected: is equal to 8192
+// // Actual: -1 (of type long), with errno 9 (Bad file number)
+// EXPECT_THAT(read(-1, buf, 8192), SyscallSucceedsWithValue(8192));
+//
+// // FAIL
+// // Value of: read(4, buf, 1)
+// // Expected: is > 4096
+// // Actual: 1 (of type long)
+// EXPECT_THAT(read(4, buf, 1), SyscallSucceedsWithValue(Gt(4096)));
+//
+// SyscallFails() matches if the syscall is unsuccessful. An unsuccessful
+// syscall is defined by a return value of -1 with a non-zero errno. Example:
+//
+// // PASS
+// EXPECT_THAT(open("/", O_RDWR), SyscallFails());
+//
+// // FAIL
+// // Value of: open("/dev/null", O_RDONLY)
+// // Expected: -1 (failure)
+// // Actual: 0 (of type int)
+// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFails());
+//
+// SyscallFailsWithErrno(m) matches if the syscall is unsuccessful, and errno
+// matches m. Example:
+//
+// // PASS
+// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EISDIR));
+//
+// // PASS
+// EXPECT_THAT(open("/etc/passwd", O_RDWR | O_DIRECTORY),
+// SyscallFailsWithErrno(AnyOf(EACCES, ENOTDIR)));
+//
+// // FAIL
+// // Value of: open("/dev/null", O_RDONLY)
+// // Expected: -1 (failure) with errno 21 (Is a directory)
+// // Actual: 0 (of type int)
+// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFailsWithErrno(EISDIR));
+//
+// // FAIL
+// // Value of: open("/", O_RDWR)
+// // Expected: -1 (failure) with errno 22 (Invalid argument)
+// // Actual: -1 (of type int), failure, but with errno 21 (Is a directory)
+// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EINVAL));
+//
+// Because the syscall matchers encode save/restore functionality, their meaning
+// should not be inverted via Not. That is, AnyOf(SyscallSucceedsWithValue(1),
+// SyscallSucceedsWithValue(2)) is permitted, but not
+// Not(SyscallFailsWithErrno(EPERM)).
+//
+// Syscalls
+// ========
+//
+// RetryEINTR wraps a function that returns -1 and sets errno on failure
+// to be automatically retried when EINTR occurs. Example:
+//
+// auto rv = RetryEINTR(waitpid)(pid, &status, 0);
+//
+// ReadFd/WriteFd/PreadFd/PwriteFd are interface-compatible wrappers around the
+// read/write/pread/pwrite syscalls to handle both EINTR and partial
+// reads/writes. Example:
+//
+// EXPECT_THAT(ReadFd(fd, &buf, size), SyscallSucceedsWithValue(size));
+//
+// General Utilities
+// =================
+//
+// ApplyVec(f, xs) returns a vector containing the result of applying function
+// `f` to each value in `xs`.
+//
+// AllBitwiseCombinations takes a variadic number of ranges containing integers
+// and returns a vector containing every integer that can be formed by ORing
+// together exactly one integer from each list. List<T> is an alias for
+// std::initializer_list<T> that makes AllBitwiseCombinations more ergonomic to
+// use with list literals (initializer lists do not otherwise participate in
+// template argument deduction). Example:
+//
+// EXPECT_THAT(
+// AllBitwiseCombinations<int>(
+// List<int>{SOCK_DGRAM, SOCK_STREAM},
+// List<int>{0, SOCK_NONBLOCK}),
+// Contains({SOCK_DGRAM, SOCK_STREAM, SOCK_DGRAM | SOCK_NONBLOCK,
+// SOCK_STREAM | SOCK_NONBLOCK}));
+//
+// VecCat takes a variadic number of containers and returns a vector containing
+// the concatenated contents.
+//
+// VecAppend takes an initial container and a variadic number of containers and
+// appends each to the initial container.
+//
+// RandomizeBuffer will use MTRandom to fill the given buffer with random bytes.
+//
+// GenerateIovecs will return the smallest number of iovec arrays for writing a
+// given total number of bytes to a file, each iovec array size up to IOV_MAX,
+// each iovec in each array pointing to the same buffer.
+
+#ifndef GVISOR_TEST_UTIL_TEST_UTIL_H_
+#define GVISOR_TEST_UTIL_TEST_UTIL_H_
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cerrno>
+#include <initializer_list>
+#include <iterator>
+#include <string>
+#include <thread> // NOLINT: using std::thread::hardware_concurrency().
+#include <utility>
+#include <vector>
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+#include "gmock/gmock.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+#include "test/util/fs_util.h"
+#include "test/util/logging.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// TestInit must be called prior to RUN_ALL_TESTS.
+//
+// This parses all arguments and adjusts argc and argv appropriately.
+//
+// TestInit may create background threads.
+void TestInit(int* argc, char*** argv);
+
+// SKIP_IF may be used to skip a test case.
+//
+// These cases are still emitted, but a SKIPPED line will appear.
+#define SKIP_IF(expr) \
+ do { \
+ if (expr) { \
+ std::cout << "\033[0;33m[ SKIPPED ]\033[m => " << #expr << std::endl; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_BEFORE_KERNEL(maj, min) \
+ do { \
+ auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); \
+ SKIP_IF(version.major < (maj) || \
+ (version.major == (maj) && version.minor < (min))); \
+ } while (0)
+
+enum class Platform {
+ kNative,
+ kKVM,
+ kPtrace,
+};
+bool IsRunningOnGvisor();
+Platform GvisorPlatform();
+
+void SetupGvisorDeathTest();
+
+struct KernelVersion {
+ int major;
+ int minor;
+ int micro;
+};
+
+bool operator==(const KernelVersion& first, const KernelVersion& second);
+
+PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_string);
+PosixErrorOr<KernelVersion> GetKernelVersion();
+
+static const size_t kPageSize = sysconf(_SC_PAGESIZE);
+
+enum class CPUVendor { kIntel, kAMD, kUnknownVendor };
+
+CPUVendor GetCPUVendor();
+
+inline int NumCPUs() { return std::thread::hardware_concurrency(); }
+
+// Converts cpu_set_t to a std::string for easy examination.
+std::string CPUSetToString(const cpu_set_t& set, size_t cpus = CPU_SETSIZE);
+
+struct OpenFd {
+ // fd is the open file descriptor number.
+ int fd = -1;
+
+ // link is the resolution of the symbolic link.
+ std::string link;
+};
+
+// Make it easier to log OpenFds to error streams.
+std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v);
+std::ostream& operator<<(std::ostream& out, OpenFd const& ofd);
+
+// Gets a detailed list of open fds for this process.
+PosixErrorOr<std::vector<OpenFd>> GetOpenFDs();
+
+// Returns the number of hard links to a path.
+PosixErrorOr<uint64_t> Links(const std::string& path);
+
+namespace internal {
+
+inline std::string ErrnoWithMessage(int const errnum) {
+ char buf[1024] = {};
+ const char* str = strerror_r(errnum, buf, sizeof(buf));
+ if (str == nullptr || str[0] == '\0') {
+ snprintf(buf, sizeof(buf), "Unknown error %d", errnum);
+ str = buf;
+ }
+ return absl::StrCat(errnum, " (", str, ")");
+}
+
+template <typename Container>
+class ElementOfMatcher {
+ public:
+ explicit ElementOfMatcher(Container container)
+ : container_(::std::move(container)) {}
+
+ template <typename T>
+ bool MatchAndExplain(T const& rv,
+ ::testing::MatchResultListener* const listener) const {
+ using std::count;
+ return count(container_.begin(), container_.end(), rv) != 0;
+ }
+
+ void DescribeTo(::std::ostream* const os) const {
+ *os << "one of {";
+ char const* sep = "";
+ for (auto const& elem : container_) {
+ *os << sep << elem;
+ sep = ", ";
+ }
+ *os << "}";
+ }
+
+ void DescribeNegationTo(::std::ostream* const os) const {
+ *os << "none of {";
+ char const* sep = "";
+ for (auto const& elem : container_) {
+ *os << sep << elem;
+ sep = ", ";
+ }
+ *os << "}";
+ }
+
+ private:
+ Container const container_;
+};
+
+template <typename E>
+class SyscallSuccessMatcher {
+ public:
+ explicit SyscallSuccessMatcher(E expected)
+ : expected_(::std::move(expected)) {}
+
+ template <typename T>
+ operator ::testing::Matcher<T>() const {
+ // E is one of three things:
+ // - T, or a type losslessly and implicitly convertible to T.
+ // - A monomorphic Matcher<T>.
+ // - A polymorphic matcher.
+ // SafeMatcherCast handles any of the above correctly.
+ //
+ // Similarly, gMock will invoke this conversion operator to obtain a
+ // monomorphic matcher (this is how polymorphic matchers are implemented).
+ return ::testing::MakeMatcher(
+ new Impl<T>(::testing::SafeMatcherCast<T>(expected_)));
+ }
+
+ private:
+ template <typename T>
+ class Impl : public ::testing::MatcherInterface<T> {
+ public:
+ explicit Impl(::testing::Matcher<T> matcher)
+ : matcher_(::std::move(matcher)) {}
+
+ bool MatchAndExplain(
+ T const& rv,
+ ::testing::MatchResultListener* const listener) const override {
+ if (rv == static_cast<decltype(rv)>(-1) && errno != 0) {
+ *listener << "with errno " << ErrnoWithMessage(errno);
+ return false;
+ }
+ bool match = matcher_.MatchAndExplain(rv, listener);
+ if (match) {
+ MaybeSave();
+ }
+ return match;
+ }
+
+ void DescribeTo(::std::ostream* const os) const override {
+ matcher_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(::std::ostream* const os) const override {
+ matcher_.DescribeNegationTo(os);
+ }
+
+ private:
+ ::testing::Matcher<T> matcher_;
+ };
+
+ private:
+ E expected_;
+};
+
+// A polymorphic matcher equivalent to ::testing::internal::AnyMatcher, except
+// not in namespace ::testing::internal, and describing SyscallSucceeds()'s
+// match constraints (which are enforced by SyscallSuccessMatcher::Impl).
+class AnySuccessValueMatcher {
+ public:
+ template <typename T>
+ operator ::testing::Matcher<T>() const {
+ return ::testing::MakeMatcher(new Impl<T>());
+ }
+
+ private:
+ template <typename T>
+ class Impl : public ::testing::MatcherInterface<T> {
+ public:
+ bool MatchAndExplain(
+ T const& rv,
+ ::testing::MatchResultListener* const listener) const override {
+ return true;
+ }
+
+ void DescribeTo(::std::ostream* const os) const override {
+ *os << "not -1 (success)";
+ }
+
+ void DescribeNegationTo(::std::ostream* const os) const override {
+ *os << "-1 (failure)";
+ }
+ };
+};
+
+class SyscallFailureMatcher {
+ public:
+ explicit SyscallFailureMatcher(::testing::Matcher<int> errno_matcher)
+ : errno_matcher_(std::move(errno_matcher)) {}
+
+ template <typename T>
+ bool MatchAndExplain(T const& rv,
+ ::testing::MatchResultListener* const listener) const {
+ if (rv != static_cast<decltype(rv)>(-1)) {
+ return false;
+ }
+ int actual_errno = errno;
+ *listener << "with errno " << ErrnoWithMessage(actual_errno);
+ bool match = errno_matcher_.MatchAndExplain(actual_errno, listener);
+ if (match) {
+ MaybeSave();
+ }
+ return match;
+ }
+
+ void DescribeTo(::std::ostream* const os) const {
+ *os << "-1 (failure), with errno ";
+ errno_matcher_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(::std::ostream* const os) const {
+ *os << "not -1 (success), with errno ";
+ errno_matcher_.DescribeNegationTo(os);
+ }
+
+ private:
+ ::testing::Matcher<int> errno_matcher_;
+};
+
+class SpecificErrnoMatcher : public ::testing::MatcherInterface<int> {
+ public:
+ explicit SpecificErrnoMatcher(int const expected) : expected_(expected) {}
+
+ bool MatchAndExplain(
+ int const actual_errno,
+ ::testing::MatchResultListener* const listener) const override {
+ return actual_errno == expected_;
+ }
+
+ void DescribeTo(::std::ostream* const os) const override {
+ *os << ErrnoWithMessage(expected_);
+ }
+
+ void DescribeNegationTo(::std::ostream* const os) const override {
+ *os << "not " << ErrnoWithMessage(expected_);
+ }
+
+ private:
+ int const expected_;
+};
+
+inline ::testing::Matcher<int> SpecificErrno(int const expected) {
+ return ::testing::MakeMatcher(new SpecificErrnoMatcher(expected));
+}
+
+} // namespace internal
+
+template <typename Container>
+inline ::testing::PolymorphicMatcher<internal::ElementOfMatcher<Container>>
+ElementOf(Container container) {
+ return ::testing::MakePolymorphicMatcher(
+ internal::ElementOfMatcher<Container>(::std::move(container)));
+}
+
+template <typename T>
+inline ::testing::PolymorphicMatcher<
+ internal::ElementOfMatcher<::std::vector<T>>>
+ElementOf(::std::initializer_list<T> elems) {
+ return ::testing::MakePolymorphicMatcher(
+ internal::ElementOfMatcher<::std::vector<T>>(::std::vector<T>(elems)));
+}
+
+template <typename E>
+inline internal::SyscallSuccessMatcher<E> SyscallSucceedsWithValue(E expected) {
+ return internal::SyscallSuccessMatcher<E>(::std::move(expected));
+}
+
+inline internal::SyscallSuccessMatcher<internal::AnySuccessValueMatcher>
+SyscallSucceeds() {
+ return SyscallSucceedsWithValue(
+ ::gvisor::testing::internal::AnySuccessValueMatcher());
+}
+
+inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher>
+SyscallFailsWithErrno(::testing::Matcher<int> expected) {
+ return ::testing::MakePolymorphicMatcher(
+ internal::SyscallFailureMatcher(::std::move(expected)));
+}
+
+// Overload taking an int so that SyscallFailsWithErrno(<specific errno>) uses
+// internal::SpecificErrno (which stringifies the errno) rather than
+// ::testing::Eq (which doesn't).
+inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher>
+SyscallFailsWithErrno(int const expected) {
+ return SyscallFailsWithErrno(internal::SpecificErrno(expected));
+}
+
+inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher>
+SyscallFails() {
+ return SyscallFailsWithErrno(::testing::Gt(0));
+}
+
+// As of GCC 7.2, -Wall => -Wc++17-compat => -Wnoexcept-type generates an
+// irrelevant, non-actionable warning about ABI compatibility when
+// RetryEINTRImpl is constructed with a noexcept function, such as glibc's
+// syscall(). See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80985.
+#if defined(__GNUC__) && !defined(__clang__) && \
+ (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2))
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnoexcept-type"
+#endif
+
+namespace internal {
+
+template <typename F>
+struct RetryEINTRImpl {
+ F const f;
+
+ explicit constexpr RetryEINTRImpl(F f) : f(std::move(f)) {}
+
+ template <typename... Args>
+ auto operator()(Args&&... args) const
+ -> decltype(f(std::forward<Args>(args)...)) {
+ while (true) {
+ errno = 0;
+ auto const ret = f(std::forward<Args>(args)...);
+ if (ret != -1 || errno != EINTR) {
+ return ret;
+ }
+ }
+ }
+};
+
+} // namespace internal
+
+template <typename F>
+constexpr internal::RetryEINTRImpl<F> RetryEINTR(F&& f) {
+ return internal::RetryEINTRImpl<F>(std::forward<F>(f));
+}
+
+#if defined(__GNUC__) && !defined(__clang__) && \
+ (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2))
+#pragma GCC diagnostic pop
+#endif
+
+namespace internal {
+
+template <typename F>
+ssize_t ApplyFileIoSyscall(F const& f, size_t const count) {
+ size_t completed = 0;
+ // `do ... while` because some callers actually want to make a syscall with a
+ // count of 0.
+ do {
+ auto const cur = RetryEINTR(f)(completed);
+ if (cur < 0) {
+ return cur;
+ } else if (cur == 0) {
+ break;
+ }
+ completed += cur;
+ } while (completed < count);
+ return completed;
+}
+
+} // namespace internal
+
+inline ssize_t ReadFd(int fd, void* buf, size_t count) {
+ return internal::ApplyFileIoSyscall(
+ [&](size_t completed) {
+ return read(fd, static_cast<char*>(buf) + completed, count - completed);
+ },
+ count);
+}
+
+inline ssize_t WriteFd(int fd, void const* buf, size_t count) {
+ return internal::ApplyFileIoSyscall(
+ [&](size_t completed) {
+ return write(fd, static_cast<char const*>(buf) + completed,
+ count - completed);
+ },
+ count);
+}
+
+inline ssize_t PreadFd(int fd, void* buf, size_t count, off_t offset) {
+ return internal::ApplyFileIoSyscall(
+ [&](size_t completed) {
+ return pread(fd, static_cast<char*>(buf) + completed, count - completed,
+ offset + completed);
+ },
+ count);
+}
+
+inline ssize_t PwriteFd(int fd, void const* buf, size_t count, off_t offset) {
+ return internal::ApplyFileIoSyscall(
+ [&](size_t completed) {
+ return pwrite(fd, static_cast<char const*>(buf) + completed,
+ count - completed, offset + completed);
+ },
+ count);
+}
+
+template <typename T>
+using List = std::initializer_list<T>;
+
+namespace internal {
+
+template <typename T>
+void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current) {
+ combinations->push_back(current);
+}
+
+template <typename T, typename Arg, typename... Args>
+void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current,
+ Arg&& next, Args&&... rest) {
+ for (auto const option : next) {
+ AppendAllBitwiseCombinations(combinations, current | option, rest...);
+ }
+}
+
+inline size_t CombinedSize(size_t accum) { return accum; }
+
+template <typename T, typename... Args>
+size_t CombinedSize(size_t accum, T const& x, Args&&... xs) {
+ return CombinedSize(accum + x.size(), std::forward<Args>(xs)...);
+}
+
+// Base case: no more containers, so do nothing.
+template <typename T>
+void DoMoveExtendContainer(T* c) {}
+
+// Append each container next to c.
+template <typename T, typename U, typename... Args>
+void DoMoveExtendContainer(T* c, U&& next, Args&&... rest) {
+ std::move(std::begin(next), std::end(next), std::back_inserter(*c));
+ DoMoveExtendContainer(c, std::forward<Args>(rest)...);
+}
+
+} // namespace internal
+
+template <typename T = int>
+std::vector<T> AllBitwiseCombinations() {
+ return std::vector<T>();
+}
+
+template <typename T = int, typename... Args>
+std::vector<T> AllBitwiseCombinations(Args&&... args) {
+ std::vector<T> combinations;
+ internal::AppendAllBitwiseCombinations(&combinations, 0, args...);
+ return combinations;
+}
+
+template <typename T, typename U, typename F>
+std::vector<T> ApplyVec(F const& f, std::vector<U> const& us) {
+ std::vector<T> vec;
+ vec.reserve(us.size());
+ for (auto const& u : us) {
+ vec.push_back(f(u));
+ }
+ return vec;
+}
+
+template <typename T, typename U>
+std::vector<T> ApplyVecToVec(std::vector<std::function<T(U)>> const& fs,
+ std::vector<U> const& us) {
+ std::vector<T> vec;
+ vec.reserve(us.size() * fs.size());
+ for (auto const& f : fs) {
+ for (auto const& u : us) {
+ vec.push_back(f(u));
+ }
+ }
+ return vec;
+}
+
+// Moves all elements from the containers `args` to the end of `c`.
+template <typename T, typename... Args>
+void VecAppend(T* c, Args&&... args) {
+ c->reserve(internal::CombinedSize(c->size(), args...));
+ internal::DoMoveExtendContainer(c, std::forward<Args>(args)...);
+}
+
+// Returns a vector containing the concatenated contents of the containers
+// `args`.
+template <typename T, typename... Args>
+std::vector<T> VecCat(Args&&... args) {
+ std::vector<T> combined;
+ VecAppend(&combined, std::forward<Args>(args)...);
+ return combined;
+}
+
+#define RETURN_ERROR_IF_SYSCALL_FAIL(syscall) \
+ do { \
+ if ((syscall) < 0 && errno != 0) { \
+ return PosixError(errno, #syscall); \
+ } \
+ } while (false)
+
+// Fill the given buffer with random bytes.
+void RandomizeBuffer(void* buffer, size_t len);
+
+template <typename T>
+inline PosixErrorOr<T> Atoi(absl::string_view str) {
+ T ret;
+ if (!absl::SimpleAtoi<T>(str, &ret)) {
+ return PosixError(EINVAL, "String not a number.");
+ }
+ return ret;
+}
+
+inline PosixErrorOr<uint64_t> AtoiBase(absl::string_view str, int base) {
+ if (base > 255 || base < 2) {
+ return PosixError(EINVAL, "Invalid Base");
+ }
+
+ uint64_t ret = 0;
+ if (!absl::numbers_internal::safe_strtou64_base(str, &ret, base)) {
+ return PosixError(EINVAL, "String not a number.");
+ }
+
+ return ret;
+}
+
+inline PosixErrorOr<double> Atod(absl::string_view str) {
+ double ret;
+ if (!absl::SimpleAtod(str, &ret)) {
+ return PosixError(EINVAL, "String not a double type.");
+ }
+ return ret;
+}
+
+inline PosixErrorOr<float> Atof(absl::string_view str) {
+ float ret;
+ if (!absl::SimpleAtof(str, &ret)) {
+ return PosixError(EINVAL, "String not a float type.");
+ }
+ return ret;
+}
+
+// Return the smallest number of iovec arrays that can be used to write
+// "total_bytes" number of bytes, each iovec writing one "buf".
+std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size,
+ void* buf, size_t buflen);
+
+// Sleep for at least the specified duration. Avoids glibc.
+void SleepSafe(absl::Duration duration);
+
+// Returns bytes in 'n' megabytes. Used for readability.
+uint64_t Megabytes(uint64_t n);
+
+// Predicate for checking that a value is within some tolerance of another
+// value. Returns true iff current is in the range [target * (1 - tolerance),
+// target * (1 + tolerance)].
+bool Equivalent(uint64_t current, uint64_t target, double tolerance);
+
+// Matcher wrapping the Equivalent predicate.
+MATCHER_P2(EquivalentWithin, target, tolerance,
+ std::string(negation ? "Isn't" : "Is") +
+ ::absl::StrFormat(" within %.2f%% of the target of %zd bytes",
+ tolerance * 100, target)) {
+ if (target == 0) {
+ *result_listener << ::absl::StreamFormat("difference of infinity%%");
+ } else {
+ int64_t delta = static_cast<int64_t>(arg) - static_cast<int64_t>(target);
+ double delta_percent =
+ static_cast<double>(delta) / static_cast<double>(target) * 100;
+ *result_listener << ::absl::StreamFormat("difference of %.2f%%",
+ delta_percent);
+ }
+ return Equivalent(arg, target, tolerance);
+}
+
+void TestInit(int* argc, char*** argv);
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_TEST_UTIL_H_
diff --git a/test/util/test_util_test.cc b/test/util/test_util_test.cc
new file mode 100644
index 000000000..5889651d1
--- /dev/null
+++ b/test/util/test_util_test.cc
@@ -0,0 +1,250 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/test_util.h"
+
+#include <errno.h>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::testing::AnyOf;
+using ::testing::Gt;
+using ::testing::IsEmpty;
+using ::testing::Lt;
+using ::testing::Not;
+using ::testing::TypedEq;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+TEST(KernelVersionParsing, ValidateParsing) {
+ KernelVersion v = ASSERT_NO_ERRNO_AND_VALUE(
+ ParseKernelVersion("4.18.10-1foo2-amd64 baz blah"));
+ ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
+
+ v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-1foo2-amd64"));
+ ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
+
+ v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-14-amd64"));
+ ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
+
+ v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-amd64"));
+ ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
+
+ v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10"));
+ ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
+
+ v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0.10"));
+ ASSERT_TRUE(v == KernelVersion({4, 0, 10}));
+
+ v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0"));
+ ASSERT_TRUE(v == KernelVersion({4, 0, 0}));
+
+ ASSERT_THAT(ParseKernelVersion("4.a"), PosixErrorIs(EINVAL, ::testing::_));
+ ASSERT_THAT(ParseKernelVersion("3"), PosixErrorIs(EINVAL, ::testing::_));
+ ASSERT_THAT(ParseKernelVersion(""), PosixErrorIs(EINVAL, ::testing::_));
+ ASSERT_THAT(ParseKernelVersion("version 3.3.10"),
+ PosixErrorIs(EINVAL, ::testing::_));
+}
+
+TEST(MatchersTest, SyscallSucceeds) {
+ EXPECT_THAT(0, SyscallSucceeds());
+ EXPECT_THAT(0L, SyscallSucceeds());
+
+ errno = 0;
+ EXPECT_THAT(-1, SyscallSucceeds());
+ EXPECT_THAT(-1L, SyscallSucceeds());
+
+ errno = ENOMEM;
+ EXPECT_THAT(-1, Not(SyscallSucceeds()));
+ EXPECT_THAT(-1L, Not(SyscallSucceeds()));
+}
+
+TEST(MatchersTest, SyscallSucceedsWithValue) {
+ EXPECT_THAT(0, SyscallSucceedsWithValue(0));
+ EXPECT_THAT(1, SyscallSucceedsWithValue(Lt(3)));
+ EXPECT_THAT(-1, Not(SyscallSucceedsWithValue(Lt(3))));
+ EXPECT_THAT(4, Not(SyscallSucceedsWithValue(Lt(3))));
+
+ // Non-int -1
+ EXPECT_THAT(-1L, Not(SyscallSucceedsWithValue(0)));
+
+ // Non-int, truncates to -1 if converted to int, with expected value
+ EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(0xffffffffL));
+
+ // Non-int, truncates to -1 if converted to int, with monomorphic matcher
+ EXPECT_THAT(0xffffffffL,
+ SyscallSucceedsWithValue(TypedEq<long>(0xffffffffL)));
+
+ // Non-int, truncates to -1 if converted to int, with polymorphic matcher
+ EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(Gt(1)));
+}
+
+TEST(MatchersTest, SyscallFails) {
+ EXPECT_THAT(0, Not(SyscallFails()));
+ EXPECT_THAT(0L, Not(SyscallFails()));
+
+ errno = 0;
+ EXPECT_THAT(-1, Not(SyscallFails()));
+ EXPECT_THAT(-1L, Not(SyscallFails()));
+
+ errno = ENOMEM;
+ EXPECT_THAT(-1, SyscallFails());
+ EXPECT_THAT(-1L, SyscallFails());
+}
+
+TEST(MatchersTest, SyscallFailsWithErrno) {
+ EXPECT_THAT(0, Not(SyscallFailsWithErrno(EINVAL)));
+ EXPECT_THAT(0L, Not(SyscallFailsWithErrno(EINVAL)));
+
+ errno = ENOMEM;
+ EXPECT_THAT(-1, Not(SyscallFailsWithErrno(EINVAL)));
+ EXPECT_THAT(-1L, Not(SyscallFailsWithErrno(EINVAL)));
+
+ errno = EINVAL;
+ EXPECT_THAT(-1, SyscallFailsWithErrno(EINVAL));
+ EXPECT_THAT(-1L, SyscallFailsWithErrno(EINVAL));
+
+ EXPECT_THAT(-1, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM)));
+ EXPECT_THAT(-1L, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM)));
+
+ std::vector<int> expected_errnos({EINVAL, ENOMEM});
+ errno = ENOMEM;
+ EXPECT_THAT(-1, SyscallFailsWithErrno(ElementOf(expected_errnos)));
+ EXPECT_THAT(-1L, SyscallFailsWithErrno(ElementOf(expected_errnos)));
+}
+
+TEST(AllBitwiseCombinationsTest, NoArguments) {
+ EXPECT_THAT(AllBitwiseCombinations(), IsEmpty());
+}
+
+TEST(AllBitwiseCombinationsTest, EmptyList) {
+ EXPECT_THAT(AllBitwiseCombinations(List<int>{}), IsEmpty());
+}
+
+TEST(AllBitwiseCombinationsTest, SingleElementList) {
+ EXPECT_THAT(AllBitwiseCombinations(List<int>{5}), UnorderedElementsAre(5));
+}
+
+TEST(AllBitwiseCombinationsTest, SingleList) {
+ EXPECT_THAT(AllBitwiseCombinations(List<int>{0, 1, 2, 4}),
+ UnorderedElementsAre(0, 1, 2, 4));
+}
+
+TEST(AllBitwiseCombinationsTest, MultipleLists) {
+ EXPECT_THAT(
+ AllBitwiseCombinations(List<int>{0, 1, 2, 3}, List<int>{0, 4, 8, 12}),
+ UnorderedElementsAreArray(
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}));
+}
+
+TEST(RandomizeBuffer, Works) {
+ const std::vector<char> original(4096);
+ std::vector<char> buffer = original;
+ RandomizeBuffer(buffer.data(), buffer.size());
+ EXPECT_NE(buffer, original);
+}
+
+// Enable comparison of vectors of iovec arrays for the following test.
+MATCHER_P(IovecsListEq, expected, "") {
+ if (arg.size() != expected.size()) {
+ *result_listener << "sizes are different (actual: " << arg.size()
+ << ", expected: " << expected.size() << ")";
+ return false;
+ }
+
+ for (uint64_t i = 0; i < expected.size(); ++i) {
+ const std::vector<struct iovec>& actual_iovecs = arg[i];
+ const std::vector<struct iovec>& expected_iovecs = expected[i];
+ if (actual_iovecs.size() != expected_iovecs.size()) {
+ *result_listener << "iovec array size at position " << i
+ << " is different (actual: " << actual_iovecs.size()
+ << ", expected: " << expected_iovecs.size() << ")";
+ return false;
+ }
+
+ for (uint64_t j = 0; j < expected_iovecs.size(); ++j) {
+ const struct iovec& actual_iov = actual_iovecs[j];
+ const struct iovec& expected_iov = expected_iovecs[j];
+ if (actual_iov.iov_base != expected_iov.iov_base) {
+ *result_listener << "iovecs in array " << i << " at position " << j
+ << " are different (expected iov_base: "
+ << expected_iov.iov_base
+ << ", got: " << actual_iov.iov_base << ")";
+ return false;
+ }
+ if (actual_iov.iov_len != expected_iov.iov_len) {
+ *result_listener << "iovecs in array " << i << " at position " << j
+ << " are different (expected iov_len: "
+ << expected_iov.iov_len
+ << ", got: " << actual_iov.iov_len << ")";
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Verify empty iovec list generation.
+TEST(GenerateIovecs, EmptyList) {
+ std::vector<char> buffer = {'a', 'b', 'c'};
+
+ EXPECT_THAT(GenerateIovecs(0, buffer.data(), buffer.size()),
+ IovecsListEq(std::vector<std::vector<struct iovec>>()));
+}
+
+// Verify generating a single array of only one, partial, iovec.
+TEST(GenerateIovecs, OneArray) {
+ std::vector<char> buffer = {'a', 'b', 'c'};
+
+ std::vector<std::vector<struct iovec>> expected;
+ struct iovec iov = {};
+ iov.iov_base = buffer.data();
+ iov.iov_len = 2;
+ expected.push_back(std::vector<struct iovec>({iov}));
+ EXPECT_THAT(GenerateIovecs(2, buffer.data(), buffer.size()),
+ IovecsListEq(expected));
+}
+
+// Verify that it wraps around after IOV_MAX iovecs.
+TEST(GenerateIovecs, WrapsAtIovMax) {
+ std::vector<char> buffer = {'a', 'b', 'c'};
+
+ std::vector<std::vector<struct iovec>> expected;
+ struct iovec iov = {};
+ iov.iov_base = buffer.data();
+ iov.iov_len = buffer.size();
+ expected.emplace_back();
+ for (int i = 0; i < IOV_MAX; ++i) {
+ expected[0].push_back(iov);
+ }
+ iov.iov_len = 1;
+ expected.push_back(std::vector<struct iovec>({iov}));
+
+ EXPECT_THAT(
+ GenerateIovecs(IOV_MAX * buffer.size() + 1, buffer.data(), buffer.size()),
+ IovecsListEq(expected));
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/thread_util.h b/test/util/thread_util.h
new file mode 100644
index 000000000..df09ac8cf
--- /dev/null
+++ b/test/util/thread_util.h
@@ -0,0 +1,89 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_THREAD_UTIL_H_
+#define GVISOR_TEST_UTIL_THREAD_UTIL_H_
+
+#include <pthread.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <functional>
+#include <utility>
+
+#include "test/util/logging.h"
+
+namespace gvisor {
+namespace testing {
+
+// ScopedThread is a minimal wrapper around pthreads.
+//
+// This is used in lieu of more complex mechanisms because it provides very
+// predictable behavior (no messing with timers, etc.) The thread will
+// automatically joined when it is destructed (goes out of scope), but can be
+// joined manually as well.
+class ScopedThread {
+ public:
+ // Constructs a thread that executes f exactly once.
+ explicit ScopedThread(std::function<void*()> f) : f_(std::move(f)) {
+ CreateThread();
+ }
+
+ explicit ScopedThread(const std::function<void()>& f) {
+ f_ = [=] {
+ f();
+ return nullptr;
+ };
+ CreateThread();
+ }
+
+ ScopedThread(const ScopedThread& other) = delete;
+ ScopedThread& operator=(const ScopedThread& other) = delete;
+
+ // Joins the thread.
+ ~ScopedThread() { Join(); }
+
+ // Waits until this thread has finished executing. Join is idempotent and may
+ // be called multiple times, however Join itself is not thread-safe.
+ void* Join() {
+ if (!joined_) {
+ TEST_PCHECK(pthread_join(pt_, &retval_) == 0);
+ joined_ = true;
+ }
+ return retval_;
+ }
+
+ private:
+ void CreateThread() {
+ TEST_PCHECK_MSG(
+ pthread_create(&pt_, /* attr = */ nullptr,
+ +[](void* arg) -> void* {
+ return static_cast<ScopedThread*>(arg)->f_();
+ },
+ this) == 0,
+ "thread creation failed");
+ }
+
+ std::function<void*()> f_;
+ pthread_t pt_;
+ bool joined_ = false;
+ void* retval_ = nullptr;
+};
+
+inline pid_t gettid() { return syscall(SYS_gettid); }
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_THREAD_UTIL_H_
diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc
new file mode 100644
index 000000000..681fafb69
--- /dev/null
+++ b/test/util/timer_util.cc
@@ -0,0 +1,27 @@
+// Copyright 2018 Google LLC
+//
+// 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 "test/util/timer_util.h"
+
+namespace gvisor {
+namespace testing {
+
+absl::Time Now(clockid_t id) {
+ struct timespec now;
+ TEST_PCHECK(clock_gettime(id, &now) == 0);
+ return absl::TimeFromTimespec(now);
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/timer_util.h b/test/util/timer_util.h
new file mode 100644
index 000000000..9bdc51a57
--- /dev/null
+++ b/test/util/timer_util.h
@@ -0,0 +1,74 @@
+// Copyright 2018 Google LLC
+//
+// 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 GVISOR_TEST_UTIL_TIMER_UTIL_H_
+#define GVISOR_TEST_UTIL_TIMER_UTIL_H_
+
+#include <errno.h>
+#include <sys/time.h>
+
+#include <functional>
+
+#include "gmock/gmock.h"
+#include "absl/time/time.h"
+#include "test/util/cleanup.h"
+#include "test/util/logging.h"
+#include "test/util/posix_error.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// MonotonicTimer is a simple timer that uses a monotic clock.
+class MonotonicTimer {
+ public:
+ MonotonicTimer() {}
+ absl::Duration Duration() {
+ struct timespec ts;
+ TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
+ return absl::TimeFromTimespec(ts) - start_;
+ }
+
+ void Start() {
+ struct timespec ts;
+ TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
+ start_ = absl::TimeFromTimespec(ts);
+ }
+
+ protected:
+ absl::Time start_;
+};
+
+// Sets the given itimer and returns a cleanup function that restores the
+// previous itimer when it goes out of scope.
+inline PosixErrorOr<Cleanup> ScopedItimer(int which,
+ struct itimerval const& new_value) {
+ struct itimerval old_value;
+ int rc = setitimer(which, &new_value, &old_value);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "setitimer failed");
+ }
+ return Cleanup(std::function<void(void)>([which, old_value] {
+ EXPECT_THAT(setitimer(which, &old_value, nullptr), SyscallSucceeds());
+ }));
+}
+
+// Returns the current time.
+absl::Time Now(clockid_t id);
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_TIMER_UTIL_H_