2017
[folly.git] / folly / experimental / TestUtil.h
1 /*
2  * Copyright 2017 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 #pragma once
18
19 #include <map>
20 #include <string>
21 #include <folly/Range.h>
22 #include <folly/ScopeGuard.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 {
94     return *path_;
95   }
96
97  private:
98   Scope scope_;
99   std::unique_ptr<fs::path> path_;
100 };
101
102 /**
103  * Changes into a temporary directory, and deletes it with all its contents
104  * upon destruction, also changing back to the original working directory.
105  */
106 class ChangeToTempDir {
107 public:
108   ChangeToTempDir();
109   ~ChangeToTempDir();
110
111   // Movable, but not copiable
112   ChangeToTempDir(ChangeToTempDir&&) = default;
113   ChangeToTempDir& operator=(ChangeToTempDir&&) = default;
114
115   const fs::path& path() const { return dir_.path(); }
116
117 private:
118   fs::path initialPath_;
119   TemporaryDirectory dir_;
120 };
121
122 namespace detail {
123 struct SavedState {
124   void* previousThreadLocalHandler;
125   int previousCrtReportMode;
126 };
127 SavedState disableInvalidParameters();
128 void enableInvalidParameters(SavedState state);
129 }
130
131 // Ok, so fun fact: The CRT on windows will actually abort
132 // on certain failed parameter validation checks in debug
133 // mode rather than simply returning -1 as it does in release
134 // mode. We can however, ensure consistent behavior by
135 // registering our own thread-local invalid parameter handler
136 // for the duration of the call, and just have that handler
137 // immediately return. We also have to disable CRT asertion
138 // alerts for the duration of the call, otherwise we get
139 // the abort-retry-ignore window.
140 template <typename Func>
141 auto msvcSuppressAbortOnInvalidParams(Func func) -> decltype(func()) {
142   auto savedState = detail::disableInvalidParameters();
143   SCOPE_EXIT {
144     detail::enableInvalidParameters(savedState);
145   };
146   return func();
147 }
148
149 /**
150  * Easy PCRE regex matching. Note that pattern must match the ENTIRE target,
151  * so use .* at the start and end of the pattern, as appropriate.  See
152  * http://regex101.com/ for a PCRE simulator.
153  */
154 #define EXPECT_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
155   EXPECT_PRED2( \
156     ::folly::test::detail::hasPCREPatternMatch, \
157     pattern_stringpiece, \
158     target_stringpiece \
159   )
160 #define EXPECT_NO_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
161   EXPECT_PRED2( \
162     ::folly::test::detail::hasNoPCREPatternMatch, \
163     pattern_stringpiece, \
164     target_stringpiece \
165   )
166
167 namespace detail {
168   bool hasPCREPatternMatch(StringPiece pattern, StringPiece target);
169   bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target);
170 }  // namespace detail
171
172 /**
173  * Use these patterns together with CaptureFD and EXPECT_PCRE_MATCH() to
174  * test for the presence (or absence) of log lines at a particular level:
175  *
176  *   CaptureFD stderr(2);
177  *   LOG(INFO) << "All is well";
178  *   EXPECT_NO_PCRE_MATCH(glogErrOrWarnPattern(), stderr.readIncremental());
179  *   LOG(ERROR) << "Uh-oh";
180  *   EXPECT_PCRE_MATCH(glogErrorPattern(), stderr.readIncremental());
181  */
182 inline std::string glogErrorPattern() { return ".*(^|\n)E[0-9].*"; }
183 inline std::string glogWarningPattern() { return ".*(^|\n)W[0-9].*"; }
184 // Error OR warning
185 inline std::string glogErrOrWarnPattern() { return ".*(^|\n)[EW][0-9].*"; }
186
187 /**
188  * Temporarily capture a file descriptor by redirecting it into a file.
189  * You can consume its entire output thus far via read(), incrementally
190  * via readIncremental(), or via callback using chunk_cob.
191  * Great for testing logging (see also glog*Pattern()).
192  */
193 class CaptureFD {
194 private:
195   struct NoOpChunkCob { void operator()(StringPiece) {} };
196 public:
197   using ChunkCob = std::function<void(folly::StringPiece)>;
198
199   /**
200    * chunk_cob is is guaranteed to consume all the captured output. It is
201    * invoked on each readIncremental(), and also on FD release to capture
202    * as-yet unread lines.  Chunks can be empty.
203    */
204   explicit CaptureFD(int fd, ChunkCob chunk_cob = NoOpChunkCob());
205   ~CaptureFD();
206
207   /**
208    * Restore the captured FD to its original state. It can be useful to do
209    * this before the destructor so that you can read() the captured data and
210    * log about it to the formerly captured stderr or stdout.
211    */
212   void release();
213
214   /**
215    * Reads the whole file into a string, but does not remove the redirect.
216    */
217   std::string read() const;
218
219   /**
220    * Read any bytes that were appended to the file since the last
221    * readIncremental.  Great for testing line-by-line output.
222    */
223   std::string readIncremental();
224
225 private:
226   ChunkCob chunkCob_;
227   TemporaryFile file_;
228
229   int fd_;
230   int oldFDCopy_;  // equal to fd_ after restore()
231
232   off_t readOffset_;  // for incremental reading
233 };
234
235 class EnvVarSaver {
236 public:
237   EnvVarSaver();
238   ~EnvVarSaver();
239 private:
240   std::map<std::string, std::string> saved_;
241 };
242
243 }  // namespace test
244 }  // namespace folly