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