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
43using namespace WebCore;
44
45namespace TestWebKitAPI {
46
47const String FileMonitorTestData("This is a test");
48const String FileMonitorRevisedData("This is some changed text for the test");
49const String FileMonitorSecondRevisedData("This is some changed text for the test");
50
51class FileMonitorTest : public testing::Test {
52public:
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
75private:
76 String m_tempFilePath;
77};
78
79static bool observedFileModification = false;
80static bool observedFileDeletion = false;
81static bool didFinish = false;
82
83static void handleFileModification()
84{
85 observedFileModification = true;
86 didFinish = true;
87}
88
89static void handleFileDeletion()
90{
91 observedFileDeletion = true;
92 didFinish = true;
93}
94
95static void resetTestState()
96{
97 observedFileModification = false;
98 observedFileDeletion = false;
99 didFinish = false;
100}
101
102static 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
113static 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
143TEST_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
185TEST_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
245TEST_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
284TEST_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
341TEST_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