Skip to content

Commit

Permalink
Updated Release v1.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
webermm committed Jun 12, 2023
1 parent 0b91d0a commit 68c4abe
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class FileChangeListenerImpl {
struct Node {
Node(
utils::u8path path = {}, Node *parent = nullptr, bool isDirectory = true, bool didFileExistOnLastWatch = false)
: parent_(parent), isDirectory_(isDirectory), didFileExistOnLastWatch_(didFileExistOnLastWatch) {
: path_(path), parent_(parent), isDirectory_(isDirectory), didFileExistOnLastWatch_(didFileExistOnLastWatch) {
}

utils::u8path path_;
Expand Down Expand Up @@ -78,6 +78,9 @@ class FileChangeListenerImpl {
void onDirectoryChanged(const QString &dirPath);
Node *createDirectoryWatches(const raco::utils::u8path &path);
void removeDirectoryWatches(Node* node);
Node *getNode(const raco::utils::u8path &path);
void updateDirectoryWatches(Node *node);


static bool fileCanBeAccessed(const raco::utils::u8path &path);
};
Expand Down
25 changes: 24 additions & 1 deletion components/libComponents/src/FileChangeListenerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ FileChangeListenerImpl::Node* FileChangeListenerImpl::createDirectoryWatches(con
return &rootNode_;
}

FileChangeListenerImpl::Node* FileChangeListenerImpl::getNode(const raco::utils::u8path& path) {
if (path != path.root_path()) {
auto node = getNode(path.parent_path());

auto it = node->children_.find(path);
assert(it != node->children_.end());
return it->second.get();
}
return &rootNode_;
}


void FileChangeListenerImpl::updateDirectoryWatches(Node* node) {
if (node->isDirectory_ && node->path_.existsDirectory()) {
addPathToWatch(QString::fromStdString(node->path_.string()));
for (const auto& [dummy, childNode] : node->children_) {
updateDirectoryWatches(childNode.get());
}
}
}

void FileChangeListenerImpl::remove(const std::string& absPath) {
const raco::utils::u8path& path(absPath);
if (fileWatcher_.removePath(QString::fromStdString(path.string()))) {
Expand Down Expand Up @@ -147,7 +168,9 @@ void FileChangeListenerImpl::onDelayedLoad() {

void FileChangeListenerImpl::onDirectoryChanged(const QString& dirPathString) {
utils::u8path dirPath(dirPathString.toStdString());
std::vector<utils::u8path> updated;
if (auto node = getNode(dirPath)) {
updateDirectoryWatches(node);
}
for (const auto& [path, entry] : watchedFiles_) {
if (dirPath.contains(path)) {
if (path.exists() != entry->didFileExistOnLastWatch_) {
Expand Down
223 changes: 164 additions & 59 deletions components/libComponents/tests/FileChangeMonitor_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,48 @@
#include "components/FileChangeMonitorImpl.h"
#include "testing/TestEnvironmentCore.h"
#include "utils/u8path.h"
#include "utils/FileUtils.h"

#include <fstream>

using namespace raco::core;

class FileChangeMonitorTest : public TestEnvironmentCore {
class BasicFileChangeMonitorTest : public TestEnvironmentCore {
protected:
bool waitForFileChangeCounterGEq(int count, int timeOutInMS = 2000) {
auto start = std::chrono::steady_clock::now();
do {
// Programmatic file/directory modifications need to be explicitly processed in a Qt event loop (QCoreApplication).

// Briefly yield, so the QFileSystemWatcher has time to react to our file changes and
// add its event to the event loop.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// Process the event the QFileSystemWatcher queued. This will cause the FileChangeListener
// to create a timer.
QCoreApplication::processEvents();

// Wait for the timer to queue its event, and then process the timer event, which eventually
// leads to the callbacks registered with FileMonitor::registerFileChangedHandler to be called.
std::this_thread::sleep_for(std::chrono::milliseconds(raco::components::FileChangeListenerImpl::DELAYED_FILE_LOAD_TIME_MSEC + 100));
QCoreApplication::processEvents();
}
while (fileChangeCounter_ < count && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() <= timeOutInMS);

return fileChangeCounter_ == count;
}

int argc = 0;
// Apparently QCoreApplication needs to be initialized before the ProjectFileChangeMonitor is created, since that
// will create signal connections which don't work on Linux otherwise (but they do work on Windows).
QCoreApplication eventLoop_{argc, nullptr};

int fileChangeCounter_{0};
std::function<void(void)> testCallback_ = [this]() { ++fileChangeCounter_; };
std::unique_ptr<raco::components::ProjectFileChangeMonitor> testFileChangeMonitor_ = std::make_unique<raco::components::ProjectFileChangeMonitor>();
std::vector<raco::components::ProjectFileChangeMonitor::UniqueListener> createdFileListeners_;
};

class FileChangeMonitorTest : public BasicFileChangeMonitorTest {
protected:
static constexpr const char* TEST_RESOURCES_FOLDER_NAME = "testresources";
static constexpr const char* TEST_FILE_NAME = "test.txt";
Expand All @@ -28,8 +64,7 @@ class FileChangeMonitorTest : public TestEnvironmentCore {
TestEnvironmentCore::SetUp();
std::filesystem::create_directory(testFolderPath_);

testFileOutputStream_ = std::ofstream(testFilePath_.string(), std::ios_base::out);
testFileOutputStream_.close();
raco::utils::file::write(testFilePath_, {});

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.string(), testCallback_));
}
Expand All @@ -47,28 +82,6 @@ class FileChangeMonitorTest : public TestEnvironmentCore {
TestEnvironmentCore::TearDown();
}

int waitForFileChangeCounterGEq(int count, int timeOutInMS = 2000) {
auto start = std::chrono::steady_clock::now();
do {
// Programmatic file/directory modifications need to be explicitly processed in a Qt event loop (QCoreApplication).

// Briefly yield, so the QFileSystemWatcher has time to react to our file changes and
// add its event to the event loop.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// Process the event the QFileSystemWatcher queued. This will cause the FileChangeListener
// to create a timer.
QCoreApplication::processEvents();

// Wait for the timer to queue its event, and then process the timer event, which eventually
// leads to the callbacks registered with FileMonitor::registerFileChangedHandler to be called.
std::this_thread::sleep_for(std::chrono::milliseconds(raco::components::FileChangeListenerImpl::DELAYED_FILE_LOAD_TIME_MSEC + 100));
QCoreApplication::processEvents();
}
while (fileChangeCounter_ < count && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() <= timeOutInMS);

return fileChangeCounter_;
}

void runRenamingRoutine(raco::utils::u8path& originPath, const char* firstRename, const char* secondRename) {
auto newFilePath = originPath;
newFilePath.replace_filename(firstRename);
Expand All @@ -77,62 +90,47 @@ class FileChangeMonitorTest : public TestEnvironmentCore {
evenNewerFilePath.replace_filename(secondRename);

std::filesystem::rename(testFilePath_, newFilePath);
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));

std::filesystem::rename(newFilePath, evenNewerFilePath);
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));

std::filesystem::rename(evenNewerFilePath, testFilePath_);
ASSERT_EQ(waitForFileChangeCounterGEq(2), 2);
ASSERT_TRUE(waitForFileChangeCounterGEq(2));

std::filesystem::rename(testFilePath_, newFilePath);
ASSERT_EQ(waitForFileChangeCounterGEq(3), 3);
ASSERT_TRUE(waitForFileChangeCounterGEq(3));
}

int argc = 0;
// Apparently QCoreApplication needs to be initialized before the ProjectFileChangeMonitor is created, since that
// will create signal connections which don't work on Linux otherwise (but they do work on Windows).
QCoreApplication eventLoop_{argc, nullptr};
std::ofstream testFileOutputStream_;
int fileChangeCounter_{0};
raco::utils::u8path testFolderPath_{test_path().append(TEST_RESOURCES_FOLDER_NAME)};
raco::utils::u8path testFilePath_{raco::utils::u8path(testFolderPath_).append(TEST_FILE_NAME)};
std::function<void(void)> testCallback_ = [this]() { ++fileChangeCounter_; };
std::unique_ptr<raco::components::ProjectFileChangeMonitor> testFileChangeMonitor_ = std::make_unique<raco::components::ProjectFileChangeMonitor>();
std::vector<raco::components::ProjectFileChangeMonitor::UniqueListener> createdFileListeners_;
};


TEST_F(FileChangeMonitorTest, InstantiationNoFileChange) {
ASSERT_EQ(fileChangeCounter_, 0);
}

TEST_F(FileChangeMonitorTest, FileModificationCreation) {
testFilePath_ = raco::utils::u8path(testFolderPath_).append("differentFile.txt");
createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.string(), testCallback_));
ASSERT_EQ(waitForFileChangeCounterGEq(0), 0);
ASSERT_TRUE(waitForFileChangeCounterGEq(0));

testFileOutputStream_ = std::ofstream(testFilePath_.string(), std::ios_base::out);
testFileOutputStream_.close();
raco::utils::file::write(testFilePath_, {});

ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}


TEST_F(FileChangeMonitorTest, FileModificationEditing) {
testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out);

testFileOutputStream_ << "Test";
testFileOutputStream_.flush();
testFileOutputStream_.close();
raco::utils::file::write(testFilePath_, "Test");

ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}


TEST_F(FileChangeMonitorTest, FileModificationDeletion) {
std::filesystem::remove(testFilePath_);
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}


Expand All @@ -142,16 +140,17 @@ TEST_F(FileChangeMonitorTest, FileModificationRenaming) {


TEST_F(FileChangeMonitorTest, FileModificationMultipleModificationsAtTheSameTime) {
testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out);
testFileOutputStream_ << "Test";
std::ofstream testFileOutputStream(testFilePath_.string(), std::ios_base::out);
testFileOutputStream << "Test";

std::ofstream otherOutputStream = std::ofstream(testFilePath_.string(), std::ios_base::app);
otherOutputStream << "Other";
otherOutputStream.close();

testFileOutputStream_.close();
testFileOutputStream.close();

ASSERT_GE(waitForFileChangeCounterGEq(1), 1); // Linux gives sometimes one, sometimes two events.
// Linux gives sometimes one, sometimes two events.
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}

#if (!defined(__linux))
Expand All @@ -163,22 +162,128 @@ TEST_F(FileChangeMonitorTest, FileModificationSetAndTryToEditReadOnly) {
ASSERT_TRUE(!ec) << "Failed to set permissons. Error code: " << ec.value() << " Error message: '" << ec.message() << "'";

// fileChangeCounter_ will be 0 in WSL, but the proper value in Linux container
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));

testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out);
ASSERT_FALSE(testFileOutputStream_.is_open());
std::ofstream testFileOutputStream(testFilePath_.string(), std::ios_base::out);
ASSERT_FALSE(testFileOutputStream.is_open());

ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}
#endif


TEST_F(FileChangeMonitorTest, FolderModificationDeletion) {
std::filesystem::remove_all(test_path().append(TEST_RESOURCES_FOLDER_NAME));
ASSERT_EQ(waitForFileChangeCounterGEq(1), 1);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}


TEST_F(FileChangeMonitorTest, FolderModificationRenaming) {
runRenamingRoutine(testFolderPath_, "newFolder", "evenNewerFolder");
}

TEST_F(BasicFileChangeMonitorTest, create_file_existing_directory) {
auto testFolderPath = test_path() / "test-folder";
std::string test_file_name = "test.txt";

std::filesystem::create_directory(testFolderPath);

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
EXPECT_EQ(fileChangeCounter_, 0);

raco::utils::file::write(testFolderPath / test_file_name, {});
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}

TEST_F(BasicFileChangeMonitorTest, create_file_no_directory) {
auto testFolderPath = test_path() / "test-folder";
std::string test_file_name = "test.txt";

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
EXPECT_EQ(fileChangeCounter_, 0);

std::filesystem::create_directory(testFolderPath);
ASSERT_TRUE(waitForFileChangeCounterGEq(0));

raco::utils::file::write(testFolderPath / test_file_name, {});
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}

TEST_F(FileChangeMonitorTest, directory_rename_disappearance) {
auto movedFolderPath = test_path() / "moved-directory";
std::filesystem::rename(testFolderPath_, movedFolderPath);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));

std::filesystem::rename(movedFolderPath, testFolderPath_);
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
}

TEST_F(BasicFileChangeMonitorTest, directory_rename_appearance) {
auto initialFolderPath = test_path() / "initial-folder";
auto testFolderPath = test_path() / "test-folder";
std::string test_file_name = "test.txt";

std::filesystem::create_directory(initialFolderPath);
raco::utils::file::write(initialFolderPath / test_file_name, {});

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
EXPECT_EQ(fileChangeCounter_, 0);

std::filesystem::rename(initialFolderPath, testFolderPath);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}

TEST_F(BasicFileChangeMonitorTest, directory_appear_file_change) {
auto initialFolderPath = test_path() / "initial-folder";
auto testFolderPath = test_path() / "test-folder";
std::string test_file_name = "test.txt";

std::filesystem::create_directory(initialFolderPath);
raco::utils::file::write(initialFolderPath / test_file_name, {});

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
EXPECT_EQ(fileChangeCounter_, 0);

std::filesystem::rename(initialFolderPath, testFolderPath);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));

raco::utils::file::write(testFolderPath / test_file_name, "Test");
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
}

TEST_F(BasicFileChangeMonitorTest, directory_appear_file_disappear) {
auto initialFolderPath = test_path() / "initial-folder";
auto testFolderPath = test_path() / "test-folder";
std::string test_file_name = "test.txt";

std::filesystem::create_directory(initialFolderPath);
raco::utils::file::write(initialFolderPath / test_file_name, {});

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
EXPECT_EQ(fileChangeCounter_, 0);

std::filesystem::rename(initialFolderPath, testFolderPath);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));

std::filesystem::rename(testFolderPath / test_file_name, testFolderPath / "new-file-name.txt");
ASSERT_TRUE(waitForFileChangeCounterGEq(2));
}

TEST_F(BasicFileChangeMonitorTest, directory_appear_file_appear) {
auto initialFolderPath = test_path() / "initial-folder";
auto testFolderPath = test_path() / "test-folder";
std::string initial_test_file_name = "initial.txt";
std::string test_file_name = "test.txt";

std::filesystem::create_directory(initialFolderPath);
raco::utils::file::write(initialFolderPath / initial_test_file_name, {});

createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler((testFolderPath / test_file_name).string(), testCallback_));
EXPECT_EQ(fileChangeCounter_, 0);

std::filesystem::rename(initialFolderPath, testFolderPath);
ASSERT_TRUE(waitForFileChangeCounterGEq(0));

std::filesystem::rename(testFolderPath / initial_test_file_name, testFolderPath / test_file_name);
ASSERT_TRUE(waitForFileChangeCounterGEq(1));
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class SceneBackend : public raco::core::SceneBackendInterface {
std::vector<SceneItemDesc> getSceneItemDescriptions() const override;
std::string getExportedObjectNames(SEditorObject editorObject) const override;

static bool discardLogicEngineMessage(std::string_view message);

private:
/**
* @brief call LogicEngine validate() and filter out warnings that RamsesComposer is
Expand Down
Loading

0 comments on commit 68c4abe

Please sign in to comment.