// 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 is an alias for // std::initializer_list that makes AllBitwiseCombinations more ergonomic to // use with list literals (initializer lists do not otherwise participate in // template argument deduction). Example: // // EXPECT_THAT( // AllBitwiseCombinations( // List{SOCK_DGRAM, SOCK_STREAM}, // List{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 #include #include #include #include #include #include #include #include #include // NOLINT: using std::thread::hardware_concurrency(). #include #include #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(); bool IsRunningWithHostinet(); #ifdef __linux__ void SetupGvisorDeathTest(); #endif struct KernelVersion { int major; int minor; int micro; }; bool operator==(const KernelVersion& first, const KernelVersion& second); PosixErrorOr ParseKernelVersion(absl::string_view vers_string); PosixErrorOr 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 const& v); std::ostream& operator<<(std::ostream& out, OpenFd const& ofd); // Gets a detailed list of open fds for this process. PosixErrorOr> GetOpenFDs(); // Returns the number of hard links to a path. PosixErrorOr Links(const std::string& path); namespace internal { template class ElementOfMatcher { public: explicit ElementOfMatcher(Container container) : container_(::std::move(container)) {} template 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 class SyscallSuccessMatcher { public: explicit SyscallSuccessMatcher(E expected) : expected_(::std::move(expected)) {} template operator ::testing::Matcher() const { // E is one of three things: // - T, or a type losslessly and implicitly convertible to T. // - A monomorphic Matcher. // - 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(::testing::SafeMatcherCast(expected_))); } private: template class Impl : public ::testing::MatcherInterface { public: explicit Impl(::testing::Matcher matcher) : matcher_(::std::move(matcher)) {} bool MatchAndExplain( T const& rv, ::testing::MatchResultListener* const listener) const override { if (rv == static_cast(-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 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 operator ::testing::Matcher() const { return ::testing::MakeMatcher(new Impl()); } private: template class Impl : public ::testing::MatcherInterface { 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 errno_matcher) : errno_matcher_(std::move(errno_matcher)) {} template bool MatchAndExplain(T const& rv, ::testing::MatchResultListener* const listener) const { if (rv != static_cast(-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 errno_matcher_; }; class SpecificErrnoMatcher : public ::testing::MatcherInterface { 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 SpecificErrno(int const expected) { return ::testing::MakeMatcher(new SpecificErrnoMatcher(expected)); } } // namespace internal template inline ::testing::PolymorphicMatcher> ElementOf(Container container) { return ::testing::MakePolymorphicMatcher( internal::ElementOfMatcher(::std::move(container))); } template inline ::testing::PolymorphicMatcher< internal::ElementOfMatcher<::std::vector>> ElementOf(::std::initializer_list elems) { return ::testing::MakePolymorphicMatcher( internal::ElementOfMatcher<::std::vector>(::std::vector(elems))); } template inline internal::SyscallSuccessMatcher SyscallSucceedsWithValue(E expected) { return internal::SyscallSuccessMatcher(::std::move(expected)); } inline internal::SyscallSuccessMatcher SyscallSucceeds() { return SyscallSucceedsWithValue( ::gvisor::testing::internal::AnySuccessValueMatcher()); } inline ::testing::PolymorphicMatcher SyscallFailsWithErrno(::testing::Matcher expected) { return ::testing::MakePolymorphicMatcher( internal::SyscallFailureMatcher(::std::move(expected))); } // Overload taking an int so that SyscallFailsWithErrno() uses // internal::SpecificErrno (which stringifies the errno) rather than // ::testing::Eq (which doesn't). inline ::testing::PolymorphicMatcher SyscallFailsWithErrno(int const expected) { return SyscallFailsWithErrno(internal::SpecificErrno(expected)); } inline ::testing::PolymorphicMatcher 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 struct RetryEINTRImpl { F const f; explicit constexpr RetryEINTRImpl(F f) : f(std::move(f)) {} template auto operator()(Args&&... args) const -> decltype(f(std::forward(args)...)) { while (true) { errno = 0; auto const ret = f(std::forward(args)...); if (ret != -1 || errno != EINTR) { return ret; } } } }; } // namespace internal template constexpr internal::RetryEINTRImpl RetryEINTR(F&& f) { return internal::RetryEINTRImpl(std::forward(f)); } #if defined(__GNUC__) && !defined(__clang__) && \ (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2)) #pragma GCC diagnostic pop #endif namespace internal { template 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(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(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(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(buf) + completed, count - completed, offset + completed); }, count); } template using List = std::initializer_list; namespace internal { template void AppendAllBitwiseCombinations(std::vector* combinations, T current) { combinations->push_back(current); } template void AppendAllBitwiseCombinations(std::vector* 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 size_t CombinedSize(size_t accum, T const& x, Args&&... xs) { return CombinedSize(accum + x.size(), std::forward(xs)...); } // Base case: no more containers, so do nothing. template void DoMoveExtendContainer(T* c) {} // Append each container next to c. template void DoMoveExtendContainer(T* c, U&& next, Args&&... rest) { std::move(std::begin(next), std::end(next), std::back_inserter(*c)); DoMoveExtendContainer(c, std::forward(rest)...); } } // namespace internal template std::vector AllBitwiseCombinations() { return std::vector(); } template std::vector AllBitwiseCombinations(Args&&... args) { std::vector combinations; internal::AppendAllBitwiseCombinations(&combinations, 0, args...); return combinations; } template std::vector ApplyVec(F const& f, std::vector const& us) { std::vector vec; vec.reserve(us.size()); for (auto const& u : us) { vec.push_back(f(u)); } return vec; } template std::vector ApplyVecToVec(std::vector> const& fs, std::vector const& us) { std::vector 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 void VecAppend(T* c, Args&&... args) { c->reserve(internal::CombinedSize(c->size(), args...)); internal::DoMoveExtendContainer(c, std::forward(args)...); } // Returns a vector containing the concatenated contents of the containers // `args`. template std::vector VecCat(Args&&... args) { std::vector combined; VecAppend(&combined, std::forward(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 inline PosixErrorOr Atoi(absl::string_view str) { T ret; if (!absl::SimpleAtoi(str, &ret)) { return PosixError(EINVAL, "String not a number."); } return ret; } inline PosixErrorOr 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 Atod(absl::string_view str) { double ret; if (!absl::SimpleAtod(str, &ret)) { return PosixError(EINVAL, "String not a double type."); } return ret; } inline PosixErrorOr 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> GenerateIovecs(uint64 total_size, void* buf, size_t buflen); // Returns bytes in 'n' megabytes. Used for readability. uint64 Megabytes(uint64 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 current, uint64 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 delta = static_cast(arg) - static_cast(target); double delta_percent = static_cast(delta) / static_cast(target) * 100; *result_listener << ::absl::StreamFormat("difference of %.2f%%", delta_percent); } return Equivalent(arg, target, tolerance); } // Returns the absolute path to the a data dependency. 'path' is the runfile // location relative to workspace root. #ifdef __linux__ std::string RunfilePath(std::string path); #endif void TestInit(int* argc, char*** argv); } // namespace testing } // namespace gvisor #endif // GVISOR_TEST_UTIL_TEST_UTIL_H_