logging: set the thread name for the AsyncFileWriter thread
[folly.git] / folly / experimental / TestUtil.h
index 30687bf72a994f9948d2fa70c7fb741c7455e323..0f1ba3ebf2dcb4a22f490137392aaf9a2ed170f4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef FOLLY_TESTUTIL_H_
-#define FOLLY_TESTUTIL_H_
+#pragma once
 
+#include <map>
 #include <string>
+
 #include <folly/Range.h>
+#include <folly/ScopeGuard.h>
 #include <folly/experimental/io/FsUtil.h>
 
 namespace folly {
@@ -48,18 +50,37 @@ class TemporaryFile {
                          bool closeOnDestruction = true);
   ~TemporaryFile();
 
-  // Movable, but not copiable
-  TemporaryFile(TemporaryFile&&) = default;
-  TemporaryFile& operator=(TemporaryFile&&) = default;
+  // Movable, but not copyable
+  TemporaryFile(TemporaryFile&& other) noexcept {
+    reset();
+    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_;
+  }
 };
 
 /**
@@ -89,11 +110,13 @@ class TemporaryDirectory {
   TemporaryDirectory(TemporaryDirectory&&) = default;
   TemporaryDirectory& operator=(TemporaryDirectory&&) = default;
 
-  const fs::path& path() const { return path_; }
+  const fs::path& path() const {
+    return *path_;
+  }
 
  private:
   Scope scope_;
-  fs::path path_;
+  std::unique_ptr<fs::path> path_;
 };
 
 /**
@@ -101,7 +124,7 @@ class TemporaryDirectory {
  * upon destruction, also changing back to the original working directory.
  */
 class ChangeToTempDir {
-public:
+ public:
   ChangeToTempDir();
   ~ChangeToTempDir();
 
@@ -111,12 +134,124 @@ public:
 
   const fs::path& path() const { return dir_.path(); }
 
-private:
+ private:
   fs::path initialPath_;
   TemporaryDirectory dir_;
 };
 
-}  // namespace test
-}  // namespace folly
+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 <typename Func>
+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<void(folly::StringPiece)>;
+
+  /**
+   * 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
+};
 
-#endif /* FOLLY_TESTUTIL_H_ */
+} // namespace test
+} // namespace folly