folly copyright 2015 -> copyright 2016
[folly.git] / folly / experimental / TestUtil.h
index 74d67a97c1ffd0f3dbfdc57a2d221cff54759807..98af4c4179a505d728f57a75b41dbfc0ad78409a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2016 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef FOLLY_TESTUTIL_H_
 #define FOLLY_TESTUTIL_H_
 
+#include <map>
 #include <string>
+#include <folly/Range.h>
+#include <folly/experimental/io/FsUtil.h>
 
 namespace folly {
 namespace test {
@@ -27,8 +30,7 @@ namespace test {
  *
  * 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
- * by making "prefix" be a path (containing a '/'; use a prefix starting with
- * './' to create a file in the current directory).
+ * 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
@@ -41,23 +43,175 @@ class TemporaryFile {
     UNLINK_IMMEDIATELY,
     UNLINK_ON_DESTRUCTION
   };
-  explicit TemporaryFile(const char* prefix=nullptr,
-                         Scope scope=Scope::UNLINK_ON_DESTRUCTION,
-                         bool closeOnDestruction=true);
+  explicit TemporaryFile(StringPiece namePrefix = StringPiece(),
+                         fs::path dir = fs::path(),
+                         Scope scope = Scope::UNLINK_ON_DESTRUCTION,
+                         bool closeOnDestruction = true);
   ~TemporaryFile();
 
+  // Movable, but not copiable
+  TemporaryFile(TemporaryFile&&) = default;
+  TemporaryFile& operator=(TemporaryFile&&) = default;
+
   int fd() const { return fd_; }
-  const std::string& path() const;
+  const fs::path& path() const;
 
  private:
   Scope scope_;
   bool closeOnDestruction_;
   int fd_;
-  std::string path_;
+  fs::path 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_;
+  fs::path 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:
+  fs::path initialPath_;
+  TemporaryDirectory dir_;
+};
+
+/**
+ * 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
+};
+
+class EnvVarSaver {
+public:
+  EnvVarSaver();
+  ~EnvVarSaver();
+private:
+  std::map<std::string, std::string> saved_;
 };
 
 }  // namespace test
 }  // namespace folly
 
 #endif /* FOLLY_TESTUTIL_H_ */
-