// 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: // // if (GvisorPlatform() == Platform::kPtrace) { // ... // } // // SetupGvisorDeathTest ensures that signal handling does not interfere with /// tests that rely on fatal signals. // // 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 { constexpr char kTestOnGvisor[] = "TEST_ON_GVISOR"; // 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) // Platform contains platform names. namespace Platform { constexpr char kNative[] = "native"; constexpr char kPtrace[] = "ptrace"; constexpr char kKVM[] = "kvm"; constexpr char kFuchsia[] = "fuchsia"; } // namespace Platform bool IsRunningOnGvisor(); const std::string GvisorPlatform(); bool IsRunningWithHostinet(); // TODO(gvisor.dev/issue/1624): Delete once VFS1 is gone. bool IsRunningWithVFS1(); #ifdef __linux__ void SetupGvisorDeathTest(); #endif 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); } // 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); int RunAllTests(void); } // namespace testing } // namespace gvisor #endif // GVISOR_TEST_UTIL_TEST_UTIL_H_