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
26namespace folly {
27namespace 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 */
40class 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
100class 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 */
129class 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
147namespace detail {
148struct SavedState {
149 void* previousThreadLocalHandler;
150 int previousCrtReportMode;
151};
152SavedState disableInvalidParameters();
153void 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.
165template <typename Func>
166auto 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
190namespace detail {
191bool hasPCREPatternMatch(StringPiece pattern, StringPiece target);
192bool 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 */
205inline std::string glogErrorPattern() {
206 return ".*(^|\n)E[0-9].*";
207}
208inline std::string glogWarningPattern() {
209 return ".*(^|\n)W[0-9].*";
210}
211// Error OR warning
212inline 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 */
222class 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