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