/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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. */ #pragma once #include #include #include #include #include namespace folly { namespace test { /** * Temporary file. * * By default, the file is created in a system-specific location (the value * of the TMPDIR environment variable, or /tmp), but you can override that * with a different (non-empty) directory passed to the constructor. * * By default, the file is closed and deleted when the TemporaryFile object * is destroyed, but both these behaviors can be overridden with arguments * to the constructor. */ class TemporaryFile { public: enum class Scope { PERMANENT, UNLINK_IMMEDIATELY, UNLINK_ON_DESTRUCTION, }; explicit TemporaryFile( StringPiece namePrefix = StringPiece(), fs::path dir = fs::path(), Scope scope = Scope::UNLINK_ON_DESTRUCTION, bool closeOnDestruction = true); ~TemporaryFile(); // Movable, but not copyable TemporaryFile(TemporaryFile&& other) noexcept { assign(other); } TemporaryFile& operator=(TemporaryFile&& other) { if (this != &other) { reset(); assign(other); } return *this; } void close(); int fd() const { return fd_; } const fs::path& path() const; void reset(); private: Scope scope_; bool closeOnDestruction_; int fd_; fs::path path_; void assign(TemporaryFile& other) { scope_ = other.scope_; closeOnDestruction_ = other.closeOnDestruction_; fd_ = std::exchange(other.fd_, -1); path_ = other.path_; } }; /** * Temporary directory. * * By default, the temporary directory is created in a system-specific * location (the value of the TMPDIR environment variable, or /tmp), but you * can override that with a non-empty directory passed to the constructor. * * By default, the directory is recursively deleted when the TemporaryDirectory * object is destroyed, but that can be overridden with an argument * to the constructor. */ class TemporaryDirectory { public: enum class Scope { PERMANENT, DELETE_ON_DESTRUCTION, }; explicit TemporaryDirectory( StringPiece namePrefix = StringPiece(), fs::path dir = fs::path(), Scope scope = Scope::DELETE_ON_DESTRUCTION); ~TemporaryDirectory(); // Movable, but not copiable TemporaryDirectory(TemporaryDirectory&&) = default; TemporaryDirectory& operator=(TemporaryDirectory&&) = default; const fs::path& path() const { return *path_; } private: Scope scope_; std::unique_ptr path_; }; /** * Changes into a temporary directory, and deletes it with all its contents * upon destruction, also changing back to the original working directory. */ class ChangeToTempDir { public: ChangeToTempDir(); ~ChangeToTempDir(); // Movable, but not copiable ChangeToTempDir(ChangeToTempDir&&) = default; ChangeToTempDir& operator=(ChangeToTempDir&&) = default; const fs::path& path() const { return dir_.path(); } private: TemporaryDirectory dir_; fs::path orig_; }; namespace detail { struct SavedState { void* previousThreadLocalHandler; int previousCrtReportMode; }; SavedState disableInvalidParameters(); void enableInvalidParameters(SavedState state); } // namespace detail // Ok, so fun fact: The CRT on windows will actually abort // on certain failed parameter validation checks in debug // mode rather than simply returning -1 as it does in release // mode. We can however, ensure consistent behavior by // registering our own thread-local invalid parameter handler // for the duration of the call, and just have that handler // immediately return. We also have to disable CRT asertion // alerts for the duration of the call, otherwise we get // the abort-retry-ignore window. template auto msvcSuppressAbortOnInvalidParams(Func func) -> decltype(func()) { auto savedState = detail::disableInvalidParameters(); SCOPE_EXIT { detail::enableInvalidParameters(savedState); }; return func(); } /** * Easy PCRE regex matching. Note that pattern must match the ENTIRE target, * so use .* at the start and end of the pattern, as appropriate. See * http://regex101.com/ for a PCRE simulator. */ #define EXPECT_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \ EXPECT_PRED2( \ ::folly::test::detail::hasPCREPatternMatch, \ pattern_stringpiece, \ target_stringpiece) #define EXPECT_NO_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \ EXPECT_PRED2( \ ::folly::test::detail::hasNoPCREPatternMatch, \ pattern_stringpiece, \ target_stringpiece) namespace detail { bool hasPCREPatternMatch(StringPiece pattern, StringPiece target); bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target); } // namespace detail /** * Use these patterns together with CaptureFD and EXPECT_PCRE_MATCH() to * test for the presence (or absence) of log lines at a particular level: * * CaptureFD stderr(2); * LOG(INFO) << "All is well"; * EXPECT_NO_PCRE_MATCH(glogErrOrWarnPattern(), stderr.readIncremental()); * LOG(ERROR) << "Uh-oh"; * EXPECT_PCRE_MATCH(glogErrorPattern(), stderr.readIncremental()); */ inline std::string glogErrorPattern() { return ".*(^|\n)E[0-9].*"; } inline std::string glogWarningPattern() { return ".*(^|\n)W[0-9].*"; } // Error OR warning inline std::string glogErrOrWarnPattern() { return ".*(^|\n)[EW][0-9].*"; } /** * Temporarily capture a file descriptor by redirecting it into a file. * You can consume its entire output thus far via read(), incrementally * via readIncremental(), or via callback using chunk_cob. * Great for testing logging (see also glog*Pattern()). */ class CaptureFD { private: struct NoOpChunkCob { void operator()(StringPiece) {} }; public: using ChunkCob = std::function; /** * chunk_cob is is guaranteed to consume all the captured output. It is * invoked on each readIncremental(), and also on FD release to capture * as-yet unread lines. Chunks can be empty. */ explicit CaptureFD(int fd, ChunkCob chunk_cob = NoOpChunkCob()); ~CaptureFD(); /** * Restore the captured FD to its original state. It can be useful to do * this before the destructor so that you can read() the captured data and * log about it to the formerly captured stderr or stdout. */ void release(); /** * Reads the whole file into a string, but does not remove the redirect. */ std::string read() const; /** * Read any bytes that were appended to the file since the last * readIncremental. Great for testing line-by-line output. */ std::string readIncremental(); private: ChunkCob chunkCob_; TemporaryFile file_; int fd_; int oldFDCopy_; // equal to fd_ after restore() off_t readOffset_; // for incremental reading }; } // namespace test } // namespace folly