Fix copyright lines
[folly.git] / folly / experimental / TestUtil.h
1 /*
2  * Copyright 2012-present 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
22 #include <folly/Range.h>
23 #include <folly/ScopeGuard.h>
24 #include <folly/experimental/io/FsUtil.h>
25
26 namespace folly {
27 namespace test {
28
29 /**
30  * Temporary file.
31  *
32  * By default, the file is created in a system-specific location (the value
33  * of the TMPDIR environment variable, or /tmp), but you can override that
34  * with a different (non-empty) directory passed to the constructor.
35  *
36  * By default, the file is closed and deleted when the TemporaryFile object
37  * is destroyed, but both these behaviors can be overridden with arguments
38  * to the constructor.
39  */
40 class TemporaryFile {
41  public:
42   enum class Scope {
43     PERMANENT,
44     UNLINK_IMMEDIATELY,
45     UNLINK_ON_DESTRUCTION
46   };
47   explicit TemporaryFile(StringPiece namePrefix = StringPiece(),
48                          fs::path dir = fs::path(),
49                          Scope scope = Scope::UNLINK_ON_DESTRUCTION,
50                          bool closeOnDestruction = true);
51   ~TemporaryFile();
52
53   // Movable, but not copyable
54   TemporaryFile(TemporaryFile&& other) noexcept {
55     assign(other);
56   }
57
58   TemporaryFile& operator=(TemporaryFile&& other) {
59     if (this != &other) {
60       reset();
61       assign(other);
62     }
63     return *this;
64   }
65
66   void close();
67   int fd() const { return fd_; }
68   const fs::path& path() const;
69   void reset();
70
71  private:
72   Scope scope_;
73   bool closeOnDestruction_;
74   int fd_;
75   fs::path path_;
76
77   void assign(TemporaryFile& other) {
78     scope_ = other.scope_;
79     closeOnDestruction_ = other.closeOnDestruction_;
80     fd_ = std::exchange(other.fd_, -1);
81     path_ = other.path_;
82   }
83 };
84
85 /**
86  * Temporary directory.
87  *
88  * By default, the temporary directory is created in a system-specific
89  * location (the value of the TMPDIR environment variable, or /tmp), but you
90  * can override that with a non-empty directory passed to the constructor.
91  *
92  * By default, the directory is recursively deleted when the TemporaryDirectory
93  * object is destroyed, but that can be overridden with an argument
94  * to the constructor.
95  */
96
97 class TemporaryDirectory {
98  public:
99   enum class Scope {
100     PERMANENT,
101     DELETE_ON_DESTRUCTION
102   };
103   explicit TemporaryDirectory(StringPiece namePrefix = StringPiece(),
104                               fs::path dir = fs::path(),
105                               Scope scope = Scope::DELETE_ON_DESTRUCTION);
106   ~TemporaryDirectory();
107
108   // Movable, but not copiable
109   TemporaryDirectory(TemporaryDirectory&&) = default;
110   TemporaryDirectory& operator=(TemporaryDirectory&&) = default;
111
112   const fs::path& path() const {
113     return *path_;
114   }
115
116  private:
117   Scope scope_;
118   std::unique_ptr<fs::path> path_;
119 };
120
121 /**
122  * Changes into a temporary directory, and deletes it with all its contents
123  * upon destruction, also changing back to the original working directory.
124  */
125 class ChangeToTempDir {
126  public:
127   ChangeToTempDir();
128   ~ChangeToTempDir();
129
130   // Movable, but not copiable
131   ChangeToTempDir(ChangeToTempDir&&) = default;
132   ChangeToTempDir& operator=(ChangeToTempDir&&) = default;
133
134   const fs::path& path() const { return dir_.path(); }
135
136  private:
137   TemporaryDirectory dir_;
138   fs::path orig_;
139 };
140
141 namespace detail {
142 struct SavedState {
143   void* previousThreadLocalHandler;
144   int previousCrtReportMode;
145 };
146 SavedState disableInvalidParameters();
147 void enableInvalidParameters(SavedState state);
148 } // namespace detail
149
150 // Ok, so fun fact: The CRT on windows will actually abort
151 // on certain failed parameter validation checks in debug
152 // mode rather than simply returning -1 as it does in release
153 // mode. We can however, ensure consistent behavior by
154 // registering our own thread-local invalid parameter handler
155 // for the duration of the call, and just have that handler
156 // immediately return. We also have to disable CRT asertion
157 // alerts for the duration of the call, otherwise we get
158 // the abort-retry-ignore window.
159 template <typename Func>
160 auto msvcSuppressAbortOnInvalidParams(Func func) -> decltype(func()) {
161   auto savedState = detail::disableInvalidParameters();
162   SCOPE_EXIT {
163     detail::enableInvalidParameters(savedState);
164   };
165   return func();
166 }
167
168 /**
169  * Easy PCRE regex matching. Note that pattern must match the ENTIRE target,
170  * so use .* at the start and end of the pattern, as appropriate.  See
171  * http://regex101.com/ for a PCRE simulator.
172  */
173 #define EXPECT_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
174   EXPECT_PRED2( \
175     ::folly::test::detail::hasPCREPatternMatch, \
176     pattern_stringpiece, \
177     target_stringpiece \
178   )
179 #define EXPECT_NO_PCRE_MATCH(pattern_stringpiece, target_stringpiece) \
180   EXPECT_PRED2( \
181     ::folly::test::detail::hasNoPCREPatternMatch, \
182     pattern_stringpiece, \
183     target_stringpiece \
184   )
185
186 namespace detail {
187 bool hasPCREPatternMatch(StringPiece pattern, StringPiece target);
188 bool hasNoPCREPatternMatch(StringPiece pattern, StringPiece target);
189 } // namespace detail
190
191 /**
192  * Use these patterns together with CaptureFD and EXPECT_PCRE_MATCH() to
193  * test for the presence (or absence) of log lines at a particular level:
194  *
195  *   CaptureFD stderr(2);
196  *   LOG(INFO) << "All is well";
197  *   EXPECT_NO_PCRE_MATCH(glogErrOrWarnPattern(), stderr.readIncremental());
198  *   LOG(ERROR) << "Uh-oh";
199  *   EXPECT_PCRE_MATCH(glogErrorPattern(), stderr.readIncremental());
200  */
201 inline std::string glogErrorPattern() { return ".*(^|\n)E[0-9].*"; }
202 inline std::string glogWarningPattern() { return ".*(^|\n)W[0-9].*"; }
203 // Error OR warning
204 inline std::string glogErrOrWarnPattern() { return ".*(^|\n)[EW][0-9].*"; }
205
206 /**
207  * Temporarily capture a file descriptor by redirecting it into a file.
208  * You can consume its entire output thus far via read(), incrementally
209  * via readIncremental(), or via callback using chunk_cob.
210  * Great for testing logging (see also glog*Pattern()).
211  */
212 class CaptureFD {
213  private:
214   struct NoOpChunkCob { void operator()(StringPiece) {} };
215
216  public:
217   using ChunkCob = std::function<void(folly::StringPiece)>;
218
219   /**
220    * chunk_cob is is guaranteed to consume all the captured output. It is
221    * invoked on each readIncremental(), and also on FD release to capture
222    * as-yet unread lines.  Chunks can be empty.
223    */
224   explicit CaptureFD(int fd, ChunkCob chunk_cob = NoOpChunkCob());
225   ~CaptureFD();
226
227   /**
228    * Restore the captured FD to its original state. It can be useful to do
229    * this before the destructor so that you can read() the captured data and
230    * log about it to the formerly captured stderr or stdout.
231    */
232   void release();
233
234   /**
235    * Reads the whole file into a string, but does not remove the redirect.
236    */
237   std::string read() const;
238
239   /**
240    * Read any bytes that were appended to the file since the last
241    * readIncremental.  Great for testing line-by-line output.
242    */
243   std::string readIncremental();
244
245  private:
246   ChunkCob chunkCob_;
247   TemporaryFile file_;
248
249   int fd_;
250   int oldFDCopy_;  // equal to fd_ after restore()
251
252   off_t readOffset_;  // for incremental reading
253 };
254
255 } // namespace test
256 } // namespace folly