blob: 228383a0f30a9b139f6aaf43f95cedd809166e6a [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Test.h"
#include "Utilities.h"
#include "WTFStringUtilities.h"
#include <WebCore/FileMonitor.h>
#include <wtf/FileSystem.h>
#include <wtf/MainThread.h>
#include <wtf/RunLoop.h>
#include <wtf/Scope.h>
#include <wtf/StringExtras.h>
#include <wtf/WorkQueue.h>
#include <wtf/text/StringBuffer.h>
// Note: Disabling iOS since 'system' is not available on that platform.
#if PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(WPE)
using namespace WebCore;
namespace TestWebKitAPI {
const String FileMonitorTestData("This is a test");
const String FileMonitorRevisedData("This is some changed text for the test");
const String FileMonitorSecondRevisedData("This is some changed text for the test");
class FileMonitorTest : public testing::Test {
public:
void SetUp() override
{
RunLoop::initializeMainRunLoop();
// create temp file
FileSystem::PlatformFileHandle handle;
m_tempFilePath = FileSystem::openTemporaryFile("tempTestFile", handle);
ASSERT_NE(handle, FileSystem::invalidPlatformFileHandle);
int rc = FileSystem::writeToFile(handle, FileMonitorTestData.utf8().data(), FileMonitorTestData.length());
ASSERT_NE(rc, -1);
FileSystem::closeFile(handle);
}
void TearDown() override
{
FileSystem::deleteFile(m_tempFilePath);
}
const String& tempFilePath() { return m_tempFilePath; }
private:
String m_tempFilePath;
};
static bool observedFileModification = false;
static bool observedFileDeletion = false;
static bool didFinish = false;
static void handleFileModification()
{
observedFileModification = true;
didFinish = true;
}
static void handleFileDeletion()
{
observedFileDeletion = true;
didFinish = true;
}
static void resetTestState()
{
observedFileModification = false;
observedFileDeletion = false;
didFinish = false;
}
static String createCommand(const String& path, const String& payload)
{
StringBuilder command;
command.appendLiteral("echo \"");
command.append(payload);
command.appendLiteral("\" > ");
command.append(path);
return command.toString();
}
static String readContentsOfFile(const String& path)
{
constexpr int bufferSize = 1024;
auto source = FileSystem::openFile(path, FileSystem::FileOpenMode::Read);
if (!FileSystem::isHandleValid(source))
return emptyString();
StringBuffer<LChar> buffer(bufferSize);
auto fileCloser = WTF::makeScopeExit([source]() {
FileSystem::PlatformFileHandle handle = source;
FileSystem::closeFile(handle);
});
// Since we control the test files, we know we only need one read
int readBytes = FileSystem::readFromFile(source, reinterpret_cast<char*>(buffer.characters()), bufferSize);
if (readBytes < 0)
return emptyString();
// Strip the trailing carriage return from the file:
if (readBytes > 1) {
int lastByte = readBytes - 1;
if (buffer[lastByte] == '\n')
buffer.shrink(lastByte);
}
ASSERT(readBytes < bufferSize);
return String::adopt(WTFMove(buffer));
}
TEST_F(FileMonitorTest, DetectChange)
{
EXPECT_TRUE(FileSystem::fileExists(tempFilePath()));
RunLoop::initializeMainRunLoop();
auto testQueue = WorkQueue::create("Test Work Queue");
auto monitor = makeUnique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) {
ASSERT(!RunLoop::isMain());
switch (type) {
case FileMonitor::FileChangeType::Modification:
handleFileModification();
break;
case FileMonitor::FileChangeType::Removal:
handleFileDeletion();
break;
}
});
testQueue->dispatch([this] () mutable {
String fileContents = readContentsOfFile(tempFilePath());
EXPECT_STREQ(FileMonitorTestData.utf8().data(), fileContents.utf8().data());
auto command = createCommand(tempFilePath(), FileMonitorRevisedData);
auto rc = system(command.utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_TRUE(observedFileModification);
EXPECT_FALSE(observedFileDeletion);
String revisedFileContents = readContentsOfFile(tempFilePath());
EXPECT_STREQ(FileMonitorRevisedData.utf8().data(), revisedFileContents.utf8().data());
resetTestState();
}
TEST_F(FileMonitorTest, DetectMultipleChanges)
{
EXPECT_TRUE(FileSystem::fileExists(tempFilePath()));
RunLoop::initializeMainRunLoop();
auto testQueue = WorkQueue::create("Test Work Queue");
auto monitor = makeUnique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) {
ASSERT(!RunLoop::isMain());
switch (type) {
case FileMonitor::FileChangeType::Modification:
handleFileModification();
break;
case FileMonitor::FileChangeType::Removal:
handleFileDeletion();
break;
}
});
testQueue->dispatch([this] () mutable {
String fileContents = readContentsOfFile(tempFilePath());
EXPECT_STREQ(FileMonitorTestData.utf8().data(), fileContents.utf8().data());
auto firstCommand = createCommand(tempFilePath(), FileMonitorRevisedData);
auto rc = system(firstCommand.utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_TRUE(observedFileModification);
EXPECT_FALSE(observedFileDeletion);
String revisedFileContents = readContentsOfFile(tempFilePath());
EXPECT_STREQ(FileMonitorRevisedData.utf8().data(), revisedFileContents.utf8().data());
resetTestState();
testQueue->dispatch([this] () mutable {
auto secondCommand = createCommand(tempFilePath(), FileMonitorSecondRevisedData);
auto rc = system(secondCommand.utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_TRUE(observedFileModification);
EXPECT_FALSE(observedFileDeletion);
String secondRevisedfileContents = readContentsOfFile(tempFilePath());
EXPECT_STREQ(FileMonitorSecondRevisedData.utf8().data(), secondRevisedfileContents.utf8().data());
resetTestState();
}
TEST_F(FileMonitorTest, DetectDeletion)
{
EXPECT_TRUE(FileSystem::fileExists(tempFilePath()));
RunLoop::initializeMainRunLoop();
auto testQueue = WorkQueue::create("Test Work Queue");
auto monitor = makeUnique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) {
ASSERT(!RunLoop::isMain());
switch (type) {
case FileMonitor::FileChangeType::Modification:
handleFileModification();
break;
case FileMonitor::FileChangeType::Removal:
handleFileDeletion();
break;
}
});
testQueue->dispatch([this] () mutable {
StringBuilder command;
command.appendLiteral("rm -f ");
command.append(tempFilePath());
auto rc = system(command.toString().utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_FALSE(observedFileModification);
EXPECT_TRUE(observedFileDeletion);
resetTestState();
}
TEST_F(FileMonitorTest, DetectChangeAndThenDelete)
{
EXPECT_TRUE(FileSystem::fileExists(tempFilePath()));
RunLoop::initializeMainRunLoop();
auto testQueue = WorkQueue::create("Test Work Queue");
auto monitor = makeUnique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) {
ASSERT(!RunLoop::isMain());
switch (type) {
case FileMonitor::FileChangeType::Modification:
handleFileModification();
break;
case FileMonitor::FileChangeType::Removal:
handleFileDeletion();
break;
}
});
testQueue->dispatch([this] () mutable {
String fileContents = readContentsOfFile(tempFilePath());
EXPECT_STREQ(FileMonitorTestData.utf8().data(), fileContents.utf8().data());
auto firstCommand = createCommand(tempFilePath(), FileMonitorRevisedData);
auto rc = system(firstCommand.utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_TRUE(observedFileModification);
EXPECT_FALSE(observedFileDeletion);
resetTestState();
testQueue->dispatch([this] () mutable {
StringBuilder command;
command.appendLiteral("rm -f ");
command.append(tempFilePath());
auto rc = system(command.toString().utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_FALSE(observedFileModification);
EXPECT_TRUE(observedFileDeletion);
resetTestState();
}
TEST_F(FileMonitorTest, DetectDeleteButNotSubsequentChange)
{
EXPECT_TRUE(FileSystem::fileExists(tempFilePath()));
RunLoop::initializeMainRunLoop();
auto testQueue = WorkQueue::create("Test Work Queue");
auto monitor = makeUnique<FileMonitor>(tempFilePath(), testQueue.copyRef(), [] (FileMonitor::FileChangeType type) {
ASSERT(!RunLoop::isMain());
switch (type) {
case FileMonitor::FileChangeType::Modification:
handleFileModification();
break;
case FileMonitor::FileChangeType::Removal:
handleFileDeletion();
break;
}
});
testQueue->dispatch([this] () mutable {
StringBuilder command;
command.appendLiteral("rm -f ");
command.append(tempFilePath());
auto rc = system(command.toString().utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
Util::run(&didFinish);
EXPECT_FALSE(observedFileModification);
EXPECT_TRUE(observedFileDeletion);
resetTestState();
testQueue->dispatch([this] () mutable {
EXPECT_FALSE(FileSystem::fileExists(tempFilePath()));
auto handle = FileSystem::openFile(tempFilePath(), FileSystem::FileOpenMode::Write);
ASSERT_NE(handle, FileSystem::invalidPlatformFileHandle);
int rc = FileSystem::writeToFile(handle, FileMonitorTestData.utf8().data(), FileMonitorTestData.length());
ASSERT_NE(rc, -1);
auto firstCommand = createCommand(tempFilePath(), FileMonitorRevisedData);
rc = system(firstCommand.utf8().data());
ASSERT_NE(rc, -1);
if (rc == -1)
didFinish = true;
});
// Set a timer to end the test, since we do not expect the file modification
// to be observed.
testQueue->dispatchAfter(500_ms, [&](void) {
EXPECT_FALSE(observedFileModification);
EXPECT_FALSE(observedFileDeletion);
didFinish = true;
});
Util::run(&didFinish);
EXPECT_FALSE(observedFileModification);
EXPECT_FALSE(observedFileDeletion);
resetTestState();
}
}
#endif