3fa6a9f5dac4ea52638ea876040b292713bd0339
[folly.git] / folly / experimental / TestUtil.h
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #ifndef FOLLY_TESTUTIL_H_
18 #define FOLLY_TESTUTIL_H_
19
20 #include <map>
21 #include <string>
22 #include <folly/Range.h>
23 #include <folly/experimental/io/FsUtil.h>
24
25 namespace folly {
26 namespace test {
27
28 /**
29  * Temporary file.
30  *
31  * By default, the file is created in a system-specific location (the value
32  * of the TMPDIR environment variable, or /tmp), but you can override that
33  * with a different (non-empty) directory passed to the constructor.
34  *
35  * By default, the file is closed and deleted when the TemporaryFile object
36  * is destroyed, but both these behaviors can be overridden with arguments
37  * to the constructor.
38  */
39 class TemporaryFile {
40  public:
41   enum class Scope {
42     PERMANENT,
43     UNLINK_IMMEDIATELY,
44     UNLINK_ON_DESTRUCTION
45   };
46   explicit TemporaryFile(StringPiece namePrefix = StringPiece(),
47                          fs::path dir = fs::path(),
48                          Scope scope = Scope::UNLINK_ON_DESTRUCTION,
49                          bool closeOnDestruction = true);
50   ~TemporaryFile();
51
52   // Movable, but not copiable
53   TemporaryFile(TemporaryFile&&) = default;
54   TemporaryFile& operator=(TemporaryFile&&) = default;
55
56   int fd() const { return fd_; }
57   const fs::path& path() const;
58
59  private:
60   Scope scope_;
61   bool closeOnDestruction_;
62   int fd_;
63   fs::path path_;
64 };
65
66 /**
67  * Temporary directory.
68  *
69  * By default, the temporary directory is created in a system-specific
70  * location (the value of the TMPDIR environment variable, or /tmp), but you
71  * can override that with a non-empty directory passed to the constructor.
72  *
73  * By default, the directory is recursively deleted when the TemporaryDirectory
74  * object is destroyed, but that can be overridden with an argument
75  * to the constructor.
76  */
77
78 class TemporaryDirectory {
79  public:
80   enum class Scope {
81     PERMANENT,
82     DELETE_ON_DESTRUCTION
83   };
84   explicit TemporaryDirectory(StringPiece namePrefix = StringPiece(),
85                               fs::path dir = fs::path(),
86                               Scope scope = Scope::DELETE_ON_DESTRUCTION);
87   ~TemporaryDirectory();
88
89   // Movable, but not copiable
90   TemporaryDirectory(TemporaryDirectory&&) = default;
91   TemporaryDirectory& operator=(TemporaryDirectory&&) = default;
92
93   const fs::path& path() const { return path_; }
94
95  private:
96   Scope scope_;
97   fs::path path_;
98 };
99
100 /**
101  * Changes into a temporary directory, and deletes it with all its contents
102  * upon destruction, also changing back to the original working directory.
103  */
104 class ChangeToTempDir {
105 public:
106   ChangeToTempDir();
107   ~ChangeToTempDir();
108
109   // Movable, but not copiable
110   ChangeToTempDir(ChangeToTempDir&&) = default;
111   ChangeToTempDir& operator=(ChangeToTempDir&&) = default;
112
113   const fs::path& path() const { return dir_.path(); }
114
115 private:
116   fs::path initialPath_;
117   TemporaryDirectory dir_;
118 };
119
120 /**
121  * Easy PCRE regex matching. Note that pattern must match the ENTIRE target,
122  * so use .* at the start and end of the pattern, as appropriate.  See
123  * http://regex101.com/ for a PCRE simulator.
124  */
125 #define EXPECT_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
126   EXPECT_PRED2( \
127     ::folly::test::detail::hasPCREPatternMatch, \
128     pattern_stringpiece, \
129     target_stringpiece \
130   )
131 #define EXPECT_NO_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
132   EXPECT_PRED2( \
133     ::folly::test::detail::hasNoPCREPatternMatch, \
134     pattern_stringpiece, \
135     target_stringpiece \
136   )
137
138 namespace detail {
139   bool hasPCREPatternMatch(StringPiece pattern, StringPiece target);
140   bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target);
141 }  // namespace detail
142
143 /**
144  * Use these patterns together with CaptureFD and EXPECT_PCRE_MATCH() to
145  * test for the presence (or absence) of log lines at a particular level:
146  *
147  *   CaptureFD stderr(2);
148  *   LOG(INFO) << "All is well";
149  *   EXPECT_NO_PCRE_MATCH(glogErrOrWarnPattern(), stderr.readIncremental());
150  *   LOG(ERROR) << "Uh-oh";
151  *   EXPECT_PCRE_MATCH(glogErrorPattern(), stderr.readIncremental());
152  */
153 inline std::string glogErrorPattern() { return ".*(^|\n)E[0-9].*"; }
154 inline std::string glogWarningPattern() { return ".*(^|\n)W[0-9].*"; }
155 // Error OR warning
156 inline std::string glogErrOrWarnPattern() { return ".*(^|\n)[EW][0-9].*"; }
157
158 /**
159  * Temporarily capture a file descriptor by redirecting it into a file.
160  * You can consume its entire output thus far via read(), incrementally
161  * via readIncremental(), or via callback using chunk_cob.
162  * Great for testing logging (see also glog*Pattern()).
163  */
164 class CaptureFD {
165 private:
166   struct NoOpChunkCob { void operator()(StringPiece) {} };
167 public:
168   using ChunkCob = std::function<void(folly::StringPiece)>;
169
170   /**
171    * chunk_cob is is guaranteed to consume all the captured output. It is
172    * invoked on each readIncremental(), and also on FD release to capture
173    * as-yet unread lines.  Chunks can be empty.
174    */
175   explicit CaptureFD(int fd, ChunkCob chunk_cob = NoOpChunkCob());
176   ~CaptureFD();
177
178   /**
179    * Restore the captured FD to its original state. It can be useful to do
180    * this before the destructor so that you can read() the captured data and
181    * log about it to the formerly captured stderr or stdout.
182    */
183   void release();
184
185   /**
186    * Reads the whole file into a string, but does not remove the redirect.
187    */
188   std::string read() const;
189
190   /**
191    * Read any bytes that were appended to the file since the last
192    * readIncremental.  Great for testing line-by-line output.
193    */
194   std::string readIncremental();
195
196 private:
197   ChunkCob chunkCob_;
198   TemporaryFile file_;
199
200   int fd_;
201   int oldFDCopy_;  // equal to fd_ after restore()
202
203   off_t readOffset_;  // for incremental reading
204 };
205
206 class EnvVarSaver {
207 public:
208   EnvVarSaver();
209   ~EnvVarSaver();
210 private:
211   std::map<std::string, std::string> saved_;
212 };
213
214 }  // namespace test
215 }  // namespace folly
216
217 #endif /* FOLLY_TESTUTIL_H_ */