diff options
Diffstat (limited to 'test/util/test_util.h')
-rw-r--r-- | test/util/test_util.h | 770 |
1 files changed, 0 insertions, 770 deletions
diff --git a/test/util/test_util.h b/test/util/test_util.h deleted file mode 100644 index b9d2dc2ba..000000000 --- a/test/util/test_util.h +++ /dev/null @@ -1,770 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// 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 "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) GTEST_SKIP() << #expr; \ - } 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 { - -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 " << PosixError(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 " << PosixError(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 << PosixError(expected_); - } - - void DescribeNegationTo(::std::ostream* const os) const override { - *os << "not " << PosixError(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); - -// 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_ |