1 | /* |
2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | |
28 | #include "Test.h" |
29 | #include "Utilities.h" |
30 | #include "WTFStringUtilities.h" |
31 | #include <WebCore/FileMonitor.h> |
32 | #include <wtf/FileSystem.h> |
33 | #include <wtf/MainThread.h> |
34 | #include <wtf/RunLoop.h> |
35 | #include <wtf/Scope.h> |
36 | #include <wtf/StringExtras.h> |
37 | #include <wtf/WorkQueue.h> |
38 | #include <wtf/text/StringBuffer.h> |
39 | |
40 | // Note: Disabling iOS since 'system' is not available on that platform. |
41 | #if PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(WPE) |
42 | |
43 | using namespace WebCore; |
44 | |
45 | namespace TestWebKitAPI { |
46 | |
47 | const String FileMonitorTestData("This is a test" ); |
48 | const String FileMonitorRevisedData("This is some changed text for the test" ); |
49 | const String FileMonitorSecondRevisedData("This is some changed text for the test" ); |
50 | |
51 | class FileMonitorTest : public testing::Test { |
52 | public: |
53 | void SetUp() override |
54 | { |
55 | RunLoop::initializeMainRunLoop(); |
56 | |
57 | // create temp file |
58 | FileSystem::PlatformFileHandle handle; |
59 | m_tempFilePath = FileSystem::openTemporaryFile("tempTestFile" , handle); |
60 | ASSERT_NE(handle, FileSystem::invalidPlatformFileHandle); |
61 | |
62 | int rc = FileSystem::writeToFile(handle, FileMonitorTestData.utf8().data(), FileMonitorTestData.length()); |
63 | ASSERT_NE(rc, -1); |
64 | |
65 | FileSystem::closeFile(handle); |
66 | } |
67 | |
68 | void TearDown() override |
69 | { |
70 | FileSystem::deleteFile(m_tempFilePath); |
71 | } |
72 | |
73 | const String& tempFilePath() { return m_tempFilePath; } |
74 | |
75 | private: |
76 | String m_tempFilePath; |
77 | }; |
78 | |
79 | static bool observedFileModification = false; |
80 | static bool observedFileDeletion = false; |
81 | static bool didFinish = false; |
82 | |
83 | static void handleFileModification() |
84 | { |
85 | observedFileModification = true; |
86 | didFinish = true; |
87 | } |
88 | |
89 | static void handleFileDeletion() |
90 | { |
91 | observedFileDeletion = true; |
92 | didFinish = true; |
93 | } |
94 | |
95 | static void resetTestState() |
96 | { |
97 | observedFileModification = false; |
98 | observedFileDeletion = false; |
99 | didFinish = false; |
100 | } |
101 | |
102 | static String createCommand(const String& path, const String& payload) |
103 | { |
104 | StringBuilder command; |
105 | command.appendLiteral("echo \"" ); |
106 | command.append(payload); |
107 | command.appendLiteral("\" > " ); |
108 | command.append(path); |
109 | |
110 | return command.toString(); |
111 | } |
112 | |
113 | static String readContentsOfFile(const String& path) |
114 | { |
115 | constexpr int bufferSize = 1024; |
116 | |
117 | auto source = FileSystem::openFile(path, FileSystem::FileOpenMode::Read); |
118 | if (!FileSystem::isHandleValid(source)) |
119 | return emptyString(); |
120 | |
121 | StringBuffer<LChar> buffer(bufferSize); |
122 | |
123 | auto fileCloser = WTF::makeScopeExit([source]() { |
124 | FileSystem::PlatformFileHandle handle = source; |
125 | FileSystem::closeFile(handle); |
126 | }); |
127 | |
128 | // Since we control the test files, we know we only need one read |
129 | int readBytes = FileSystem::readFromFile(source, reinterpret_cast<char*>(buffer.characters()), bufferSize); |
130 | if (readBytes < 0) |
131 | return emptyString(); |
132 | |
133 | // Strip the trailing carriage return from the file: |
134 | if (readBytes > 1) { |
135 | int lastByte = readBytes - 1; |
136 | if (buffer[lastByte] == '\n') |
137 | buffer.shrink(lastByte); |
138 | } |
139 | ASSERT(readBytes < bufferSize); |
140 | return String::adopt(WTFMove(buffer)); |
141 | } |
142 | |
143 | TEST_F(FileMonitorTest, DetectChange) |
144 | { |
145 | EXPECT_TRUE(FileSystem::fileExists(tempFilePath())); |
146 | |
147 | RunLoop::initializeMainRunLoop(); |
148 | |
149 | auto testQueue = WorkQueue::create("Test Work Queue" ); |
150 | |
151 | auto monitor = std::make_unique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) { |
152 | ASSERT(!RunLoop::isMain()); |
153 | switch (type) { |
154 | case FileMonitor::FileChangeType::Modification: |
155 | handleFileModification(); |
156 | break; |
157 | case FileMonitor::FileChangeType::Removal: |
158 | handleFileDeletion(); |
159 | break; |
160 | } |
161 | }); |
162 | |
163 | testQueue->dispatch([this] () mutable { |
164 | String fileContents = readContentsOfFile(tempFilePath()); |
165 | EXPECT_STREQ(FileMonitorTestData.utf8().data(), fileContents.utf8().data()); |
166 | |
167 | auto command = createCommand(tempFilePath(), FileMonitorRevisedData); |
168 | auto rc = system(command.utf8().data()); |
169 | ASSERT_NE(rc, -1); |
170 | if (rc == -1) |
171 | didFinish = true; |
172 | }); |
173 | |
174 | Util::run(&didFinish); |
175 | |
176 | EXPECT_TRUE(observedFileModification); |
177 | EXPECT_FALSE(observedFileDeletion); |
178 | |
179 | String revisedFileContents = readContentsOfFile(tempFilePath()); |
180 | EXPECT_STREQ(FileMonitorRevisedData.utf8().data(), revisedFileContents.utf8().data()); |
181 | |
182 | resetTestState(); |
183 | } |
184 | |
185 | TEST_F(FileMonitorTest, DetectMultipleChanges) |
186 | { |
187 | EXPECT_TRUE(FileSystem::fileExists(tempFilePath())); |
188 | |
189 | RunLoop::initializeMainRunLoop(); |
190 | |
191 | auto testQueue = WorkQueue::create("Test Work Queue" ); |
192 | |
193 | auto monitor = std::make_unique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) { |
194 | ASSERT(!RunLoop::isMain()); |
195 | switch (type) { |
196 | case FileMonitor::FileChangeType::Modification: |
197 | handleFileModification(); |
198 | break; |
199 | case FileMonitor::FileChangeType::Removal: |
200 | handleFileDeletion(); |
201 | break; |
202 | } |
203 | }); |
204 | |
205 | testQueue->dispatch([this] () mutable { |
206 | String fileContents = readContentsOfFile(tempFilePath()); |
207 | EXPECT_STREQ(FileMonitorTestData.utf8().data(), fileContents.utf8().data()); |
208 | |
209 | auto firstCommand = createCommand(tempFilePath(), FileMonitorRevisedData); |
210 | auto rc = system(firstCommand.utf8().data()); |
211 | ASSERT_NE(rc, -1); |
212 | if (rc == -1) |
213 | didFinish = true; |
214 | }); |
215 | |
216 | Util::run(&didFinish); |
217 | |
218 | EXPECT_TRUE(observedFileModification); |
219 | EXPECT_FALSE(observedFileDeletion); |
220 | |
221 | String revisedFileContents = readContentsOfFile(tempFilePath()); |
222 | EXPECT_STREQ(FileMonitorRevisedData.utf8().data(), revisedFileContents.utf8().data()); |
223 | |
224 | resetTestState(); |
225 | |
226 | testQueue->dispatch([this] () mutable { |
227 | auto secondCommand = createCommand(tempFilePath(), FileMonitorSecondRevisedData); |
228 | auto rc = system(secondCommand.utf8().data()); |
229 | ASSERT_NE(rc, -1); |
230 | if (rc == -1) |
231 | didFinish = true; |
232 | }); |
233 | |
234 | Util::run(&didFinish); |
235 | |
236 | EXPECT_TRUE(observedFileModification); |
237 | EXPECT_FALSE(observedFileDeletion); |
238 | |
239 | String secondRevisedfileContents = readContentsOfFile(tempFilePath()); |
240 | EXPECT_STREQ(FileMonitorSecondRevisedData.utf8().data(), secondRevisedfileContents.utf8().data()); |
241 | |
242 | resetTestState(); |
243 | } |
244 | |
245 | TEST_F(FileMonitorTest, DetectDeletion) |
246 | { |
247 | EXPECT_TRUE(FileSystem::fileExists(tempFilePath())); |
248 | |
249 | RunLoop::initializeMainRunLoop(); |
250 | |
251 | auto testQueue = WorkQueue::create("Test Work Queue" ); |
252 | |
253 | auto monitor = std::make_unique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) { |
254 | ASSERT(!RunLoop::isMain()); |
255 | switch (type) { |
256 | case FileMonitor::FileChangeType::Modification: |
257 | handleFileModification(); |
258 | break; |
259 | case FileMonitor::FileChangeType::Removal: |
260 | handleFileDeletion(); |
261 | break; |
262 | } |
263 | }); |
264 | |
265 | testQueue->dispatch([this] () mutable { |
266 | StringBuilder command; |
267 | command.appendLiteral("rm -f " ); |
268 | command.append(tempFilePath()); |
269 | |
270 | auto rc = system(command.toString().utf8().data()); |
271 | ASSERT_NE(rc, -1); |
272 | if (rc == -1) |
273 | didFinish = true; |
274 | }); |
275 | |
276 | Util::run(&didFinish); |
277 | |
278 | EXPECT_FALSE(observedFileModification); |
279 | EXPECT_TRUE(observedFileDeletion); |
280 | |
281 | resetTestState(); |
282 | } |
283 | |
284 | TEST_F(FileMonitorTest, DetectChangeAndThenDelete) |
285 | { |
286 | EXPECT_TRUE(FileSystem::fileExists(tempFilePath())); |
287 | |
288 | RunLoop::initializeMainRunLoop(); |
289 | |
290 | auto testQueue = WorkQueue::create("Test Work Queue" ); |
291 | |
292 | auto monitor = std::make_unique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) { |
293 | ASSERT(!RunLoop::isMain()); |
294 | switch (type) { |
295 | case FileMonitor::FileChangeType::Modification: |
296 | handleFileModification(); |
297 | break; |
298 | case FileMonitor::FileChangeType::Removal: |
299 | handleFileDeletion(); |
300 | break; |
301 | } |
302 | }); |
303 | |
304 | testQueue->dispatch([this] () mutable { |
305 | String fileContents = readContentsOfFile(tempFilePath()); |
306 | EXPECT_STREQ(FileMonitorTestData.utf8().data(), fileContents.utf8().data()); |
307 | |
308 | auto firstCommand = createCommand(tempFilePath(), FileMonitorRevisedData); |
309 | auto rc = system(firstCommand.utf8().data()); |
310 | ASSERT_NE(rc, -1); |
311 | if (rc == -1) |
312 | didFinish = true; |
313 | }); |
314 | |
315 | Util::run(&didFinish); |
316 | |
317 | EXPECT_TRUE(observedFileModification); |
318 | EXPECT_FALSE(observedFileDeletion); |
319 | |
320 | resetTestState(); |
321 | |
322 | testQueue->dispatch([this] () mutable { |
323 | StringBuilder command; |
324 | command.appendLiteral("rm -f " ); |
325 | command.append(tempFilePath()); |
326 | |
327 | auto rc = system(command.toString().utf8().data()); |
328 | ASSERT_NE(rc, -1); |
329 | if (rc == -1) |
330 | didFinish = true; |
331 | }); |
332 | |
333 | Util::run(&didFinish); |
334 | |
335 | EXPECT_FALSE(observedFileModification); |
336 | EXPECT_TRUE(observedFileDeletion); |
337 | |
338 | resetTestState(); |
339 | } |
340 | |
341 | TEST_F(FileMonitorTest, DetectDeleteButNotSubsequentChange) |
342 | { |
343 | EXPECT_TRUE(FileSystem::fileExists(tempFilePath())); |
344 | |
345 | RunLoop::initializeMainRunLoop(); |
346 | |
347 | auto testQueue = WorkQueue::create("Test Work Queue" ); |
348 | |
349 | auto monitor = std::make_unique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) { |
350 | ASSERT(!RunLoop::isMain()); |
351 | switch (type) { |
352 | case FileMonitor::FileChangeType::Modification: |
353 | handleFileModification(); |
354 | break; |
355 | case FileMonitor::FileChangeType::Removal: |
356 | handleFileDeletion(); |
357 | break; |
358 | } |
359 | }); |
360 | |
361 | testQueue->dispatch([this] () mutable { |
362 | StringBuilder command; |
363 | command.appendLiteral("rm -f " ); |
364 | command.append(tempFilePath()); |
365 | |
366 | auto rc = system(command.toString().utf8().data()); |
367 | ASSERT_NE(rc, -1); |
368 | if (rc == -1) |
369 | didFinish = true; |
370 | }); |
371 | |
372 | Util::run(&didFinish); |
373 | |
374 | EXPECT_FALSE(observedFileModification); |
375 | EXPECT_TRUE(observedFileDeletion); |
376 | |
377 | resetTestState(); |
378 | |
379 | testQueue->dispatch([this] () mutable { |
380 | EXPECT_FALSE(FileSystem::fileExists(tempFilePath())); |
381 | |
382 | auto handle = FileSystem::openFile(tempFilePath(), FileSystem::FileOpenMode::Write); |
383 | ASSERT_NE(handle, FileSystem::invalidPlatformFileHandle); |
384 | |
385 | int rc = FileSystem::writeToFile(handle, FileMonitorTestData.utf8().data(), FileMonitorTestData.length()); |
386 | ASSERT_NE(rc, -1); |
387 | |
388 | auto firstCommand = createCommand(tempFilePath(), FileMonitorRevisedData); |
389 | rc = system(firstCommand.utf8().data()); |
390 | ASSERT_NE(rc, -1); |
391 | if (rc == -1) |
392 | didFinish = true; |
393 | }); |
394 | |
395 | // Set a timer to end the test, since we do not expect the file modification |
396 | // to be observed. |
397 | testQueue->dispatchAfter(500_ms, [&](void) { |
398 | EXPECT_FALSE(observedFileModification); |
399 | EXPECT_FALSE(observedFileDeletion); |
400 | didFinish = true; |
401 | }); |
402 | |
403 | Util::run(&didFinish); |
404 | |
405 | EXPECT_FALSE(observedFileModification); |
406 | EXPECT_FALSE(observedFileDeletion); |
407 | |
408 | resetTestState(); |
409 | } |
410 | |
411 | } |
412 | |
413 | #endif |
414 | |
415 | |