diff --git a/Sofa/framework/Helper/src/sofa/helper/system/FileSystem.cpp b/Sofa/framework/Helper/src/sofa/helper/system/FileSystem.cpp index 08af716eda9..215600525ba 100644 --- a/Sofa/framework/Helper/src/sofa/helper/system/FileSystem.cpp +++ b/Sofa/framework/Helper/src/sofa/helper/system/FileSystem.cpp @@ -24,46 +24,39 @@ #include #include -#if __has_include() - #include - namespace fs = std::filesystem; -#elif __has_include() - #include - namespace fs = std::experimental::filesystem; -#else - error "Missing the header." -#endif - +#include #include #include #ifdef WIN32 # include # include # include -# include "Shlwapi.h" // for PathFileExists() -#include +# include "Shlwapi.h" +# include #else # include # include # include # include -# include // for strerror() +# include # include #endif #if defined(__APPLE__) -#include -#include +# include +# include #endif #ifdef linux -#include -#include +# include +# include #endif #include #include +namespace fs = std::filesystem; + namespace sofa { namespace helper @@ -71,6 +64,46 @@ namespace helper namespace system { +static bool pathHasDrive(const std::string& path) { + return path.length() >=3 + && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) + && path[1] == ':'; +} + +static std::string pathWithoutDrive(const std::string& path) { + return path.substr(2, std::string::npos); +} + +static std::string pathDrive(const std::string& path) { + return path.substr(0, 2); +} + +// Note: This function uses manual string manipulation instead of std::filesystem::path::parent_path() +// because std::filesystem does not handle trailing slashes the same way. +// For example, std::filesystem treats "/abc/def/ghi/" and "/abc/def/ghi" differently for parent_path(), +// while this implementation normalizes trailing slashes before computing the parent. +// Additionally, Windows drive letters (e.g., "C:/") require special handling to preserve the drive prefix. +static std::string computeParentDirectory(const std::string& path) +{ + if (path == "") + return "."; + else if (path == "/") + return "/"; + else if (path[path.length()-1] == '/') + return computeParentDirectory(path.substr(0, path.length() - 1)); + else { + const size_t last_slash = path.find_last_of('/'); + if (last_slash == std::string::npos) + return "."; + else if (last_slash == 0) + return "/"; + else if (last_slash == path.length()) + return ""; + else + return path.substr(0, last_slash); + } +} + std::string FileSystem::getExtension(const std::string& filename) { const std::string s = filename; @@ -81,284 +114,132 @@ std::string FileSystem::getExtension(const std::string& filename) return s.substr(pos+1); } - -#if defined(WIN32) -// Helper: call FindFirstFile, taking care of wstring to string conversion. -static HANDLE helper_FindFirstFile(std::string path, WIN32_FIND_DATA *ffd) -{ - TCHAR szDir[MAX_PATH]; - HANDLE hFind = INVALID_HANDLE_VALUE; - - // Prepare string for use with FindFile functions. First, copy the - // string to a buffer, then append '\*' to the directory name. - StringCchCopy(szDir, MAX_PATH, sofa::helper::widenString(path).c_str()); - StringCchCat(szDir, MAX_PATH, TEXT("\\*")); - - // Find the first file in the directory. - hFind = FindFirstFile(szDir, ffd); - - return hFind; -} -#endif - - bool FileSystem::listDirectory(const std::string& directoryPath, std::vector& outputFilenames) { -#if defined(WIN32) - // Find the first file in the directory. - WIN32_FIND_DATA ffd; - const HANDLE hFind = helper_FindFirstFile(directoryPath, &ffd); - if (hFind == INVALID_HANDLE_VALUE) { - msg_error("FileSystem::listDirectory()") << directoryPath << ": " << Utils::GetLastError(); + try + { + for (const auto& entry : fs::directory_iterator(directoryPath)) + { + const std::string filename = entry.path().filename().string(); + if (filename != "." && filename != "..") + outputFilenames.push_back(filename); + } return false; } - - // Iterate over files and push them in the output vector - do { - std::string filename = sofa::helper::narrowString(ffd.cFileName); - if (filename != "." && filename != "..") - outputFilenames.push_back(filename); - } while (FindNextFile(hFind, &ffd) != 0); - - // Check for errors - const bool errorOccured = ::GetLastError() != ERROR_NO_MORE_FILES; - if (errorOccured) - msg_error("FileSystem::listDirectory()") << directoryPath << ": " << Utils::GetLastError(); - - FindClose(hFind); - return errorOccured; -#else - DIR *dp = opendir(directoryPath.c_str()); - if (dp == nullptr) { - msg_error("FileSystem::listDirectory()") << directoryPath << ": " << strerror(errno); + catch (const fs::filesystem_error& e) + { + msg_error("FileSystem::listDirectory()") << directoryPath << ": " << e.what(); return true; } - - struct dirent *ep; - while ( (ep = readdir(dp)) ) { - const std::string filename(ep->d_name); - if (filename != "." && filename != "..") - outputFilenames.push_back(std::string(ep->d_name)); - } - closedir(dp); - return false; -#endif } bool FileSystem::createDirectory(const std::string& path) { - std::string error = "FileSystem::createdirectory()"; -#ifdef WIN32 - if (CreateDirectory(sofa::helper::widenString(path).c_str(), nullptr) == 0) - { - DWORD errorCode = ::GetLastError(); - if (errorCode != ERROR_ALREADY_EXISTS) - { - msg_error(error) << path << ": " << Utils::GetLastError(); - return true; - } - else - { - // Check if the existing item is a file or directory - DWORD attributes = GetFileAttributes(sofa::helper::widenString(path).c_str()); - if (attributes != INVALID_FILE_ATTRIBUTES) - { - if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) - { - // It's a file, not a directory - this is an error - msg_error(error) << path << ": File exists and is not a directory"; - return true; - } - else - { - // It's already a directory - success - return false; - } - } - else - { - // Couldn't get attributes - treat as error - msg_error(error) << path << ": " << Utils::GetLastError(); - return true; - } - } - } -#else - int status = mkdir(path.c_str(), 0755); - if(status) + try { - if (errno != EEXIST) + if (fs::exists(path)) { - msg_error(error) << path << ": " << strerror(errno); - return true; - } - else - { - struct stat st_buf; - if (stat(path.c_str(), &st_buf) == 0) - { - if ((st_buf.st_mode & S_IFMT) != S_IFDIR) - { - msg_error(error) << path << ": File exists and is not a directory"; - return true; - } - else - { - // 'path' was already created and is a folder - return false; - } - } - else + if (!fs::is_directory(path)) { - msg_error(error) << path << ": Unknown error while trying to create this directory."; + msg_error("FileSystem::createDirectory()") << path << ": File exists and is not a directory"; return true; } + return false; } + fs::create_directories(path); + return false; } -#endif - else + catch (const fs::filesystem_error& e) { - // 'path' has been created sucessfully - return false; + msg_error("FileSystem::createDirectory()") << path << ": " << e.what(); + return true; } } - bool FileSystem::removeDirectory(const std::string& path) { #ifdef WIN32 if (RemoveDirectory(sofa::helper::widenString(path).c_str()) == 0) { - msg_error("FileSystem::removedirectory()") << path << ": " << Utils::GetLastError(); + msg_error("FileSystem::removeDirectory()") << path << ": " << Utils::GetLastError(); return true; } #else if (rmdir(path.c_str())) { - msg_error("FileSystem::removedirectory()") << path << ": " << strerror(errno); + msg_error("FileSystem::removeDirectory()") << path << ": " << strerror(errno); return true; } #endif return false; } +bool FileSystem::removeAll(const std::string& path){ + try + { + fs::remove_all(path); + return true ; + } + catch(const fs::filesystem_error& /*e*/) + { + return false ; + } +} -bool FileSystem::exists(const std::string& path, [[maybe_unused]] bool quiet) +bool FileSystem::removeFile(const std::string& path) { -#if defined(WIN32) - ::SetLastError(0); - if (PathFileExists(sofa::helper::widenString(path).c_str()) != 0) - return true; - else + try { - const DWORD errorCode = ::GetLastError(); - if (errorCode != ERROR_FILE_NOT_FOUND && errorCode != ERROR_PATH_NOT_FOUND) // not No such file error - msg_error("FileSystem::exists()") << path << ": " << Utils::GetLastError(); + return fs::remove(path); + } + catch (const fs::filesystem_error& e) + { + msg_error("FileSystem::removeFile()") << path << ": " << e.what(); return false; } - -#else - struct stat st_buf; - if (stat(path.c_str(), &st_buf) == 0) - return true; - else - if (errno == ENOENT) // No such file or directory - return false; - else { - if (!quiet) - msg_error("FileSystem::exists()") << path << ": " << strerror(errno); - return false; - } -#endif } - -bool FileSystem::isDirectory(const std::string& path, [[maybe_unused]] bool quiet) +bool FileSystem::exists(const std::string& path, bool quiet) { -#if defined(WIN32) - const DWORD fileAttrib = GetFileAttributes(sofa::helper::widenString(path).c_str()); - if (fileAttrib == INVALID_FILE_ATTRIBUTES) { - msg_error("FileSystem::isDirectory()") << path << ": " << Utils::GetLastError(); - return false; + try + { + return fs::exists(path); } - else - return (fileAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0; -#else - struct stat st_buf; - if (stat(path.c_str(), &st_buf) != 0) { + catch (const fs::filesystem_error& e) + { if (!quiet) - msg_error("FileSystem::isDirectory()") << path << ": " << strerror(errno); + msg_error("FileSystem::exists()") << path << ": " << e.what(); return false; } - else - return S_ISDIR(st_buf.st_mode); -#endif } -bool FileSystem::listDirectory(const std::string& directoryPath, - std::vector& outputFilenames, - const std::string& extension) +bool FileSystem::isDirectory(const std::string& path, bool quiet) { - // List directory - std::vector files; - if (listDirectory(directoryPath, files)) - return true; - - // Filter files - for (std::size_t i=0 ; i!=files.size() ; i++) { - const std::string& filename = files[i]; - if (filename.size() >= extension.size()) - if (filename.compare(filename.size()-extension.size(), - std::string::npos, extension) == 0) - outputFilenames.push_back(filename); + try + { + return fs::is_directory(path); + } + catch (const fs::filesystem_error& e) + { + if (!quiet) + msg_error("FileSystem::isDirectory()") << path << ": " << e.what(); + return false; } - return false; } -int FileSystem::findFiles(const std::string& directoryPath, - std::vector& outputFilePaths, - const std::string& extension, const int depth) +bool FileSystem::isFile(const std::string &path, bool quiet) { - // List directory - std::vector files; - if (listDirectory(directoryPath, files)) // true = error - return -1; - - // Filter files - for (const auto& filename : files) + try { - const std::string& filepath = append(directoryPath, filename); - - if ( isDirectory(filepath) && filename[0] != '.' && depth > 0 ) - { - if ( findFiles(filepath, outputFilePaths, extension, depth - 1) == -1) - return -1; - } - else if ( isFile(filepath) && - filename.length() >= extension.length() && - filename.compare(filename.length() - extension.length(), extension.length(), extension) == 0 ) - { - // filename ends with extension - outputFilePaths.push_back(filepath); - } + return fs::is_regular_file(path); + } + catch (const fs::filesystem_error& e) + { + if (!quiet) + msg_error("FileSystem::isFile()") << path << ": " << e.what(); + return false; } - return (int)outputFilePaths.size(); -} - - -static bool pathHasDrive(const std::string& path) { - return path.length() >=3 - && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) - && path[1] == ':'; -} - -static std::string pathWithoutDrive(const std::string& path) { - return path.substr(2, std::string::npos); -} - -static std::string pathDrive(const std::string& path) { - return path.substr(0, 2); } bool FileSystem::isAbsolute(const std::string& path) @@ -368,14 +249,6 @@ bool FileSystem::isAbsolute(const std::string& path) || path[0] == '/'); } -bool FileSystem::isFile(const std::string &path, bool quiet) -{ - return - FileSystem::exists(path, quiet) && - !FileSystem::isDirectory(path, quiet) - ; -} - std::string FileSystem::convertBackSlashesToSlashes(const std::string& path) { std::string str(path); @@ -390,23 +263,6 @@ std::string FileSystem::convertSlashesToBackSlashes(const std::string& path) return str; } -bool FileSystem::removeAll(const std::string& path){ - try - { - fs::remove_all(path); - } - catch(fs::filesystem_error const & /*e*/) - { - return false ; - } - return true ; -} - -bool FileSystem::removeFile(const std::string& path) -{ - return fs::remove(path); -} - std::string FileSystem::removeExtraSlashes(const std::string& path) { std::string str = path; @@ -443,29 +299,6 @@ std::string FileSystem::removeExtraBackSlashes(const std::string& path) return str; } -void FileSystem::ensureFolderExists(const std::string& pathToFolder) -{ - if (!FileSystem::exists(pathToFolder)) - { - const std::string parentPath = FileSystem::getParentDirectory(pathToFolder); - FileSystem::ensureFolderExists(parentPath); - - if (FileSystem::exists(parentPath)) - { - FileSystem::createDirectory(pathToFolder); - } - } -} - -void FileSystem::ensureFolderForFileExists(const std::string& pathToFile) -{ - if (!FileSystem::exists(pathToFile)) - { - const std::string parentPath = FileSystem::getParentDirectory(pathToFile); - FileSystem::ensureFolderExists(parentPath); - } -} - std::string FileSystem::cleanPath(const std::string& path, separator s) { if(s == SLASH) @@ -474,52 +307,128 @@ std::string FileSystem::cleanPath(const std::string& path, separator s) return removeExtraBackSlashes(convertSlashesToBackSlashes(path)); } -static std::string computeParentDirectory(const std::string& path) -{ - if (path == "") - return "."; - else if (path == "/") - return "/"; - else if (path[path.length()-1] == '/') - return computeParentDirectory(path.substr(0, path.length() - 1)); - else { - const size_t last_slash = path.find_last_of('/'); - if (last_slash == std::string::npos) - return "."; - else if (last_slash == 0) - return "/"; - else if (last_slash == path.length()) - return ""; - else - return path.substr(0, last_slash); - } -} - std::string FileSystem::getParentDirectory(const std::string& path) { - if (pathHasDrive(path)) // check for Windows drive + if (pathHasDrive(path)) return pathDrive(path) + computeParentDirectory(pathWithoutDrive(path)); else return computeParentDirectory(path); } +// Note: This function uses manual string manipulation instead of std::filesystem::path::filename() +// because std::filesystem does not handle trailing slashes correctly. +// For example, std::filesystem::path("/abc/def/ghi/").filename() returns "" (empty), +// while this implementation returns "ghi" by recursively stripping trailing slashes first. +// Windows drive letters also require special handling. std::string FileSystem::stripDirectory(const std::string& path) { - if (pathHasDrive(path)) // check for Windows drive + if (pathHasDrive(path)) return stripDirectory(pathWithoutDrive(path)); else { const size_t last_slash = path.find_last_of("/"); - if (last_slash == std::string::npos) // No slash + if (last_slash == std::string::npos) return path; - else if (last_slash == path.size() - 1) // Trailing slash + else if (last_slash == path.size() - 1) if (path.size() == 1) return "/"; else return stripDirectory(path.substr(0, path.size() - 1)); else return path.substr(last_slash + 1, std::string::npos); - return ""; + } +} + +bool FileSystem::listDirectory(const std::string& directoryPath, + std::vector& outputFilenames, + const std::string& extension) +{ + try + { + for (const auto& entry : fs::directory_iterator(directoryPath)) + { + if (entry.is_regular_file()) + { + const std::string filename = entry.path().filename().string(); + if (filename.size() >= extension.size()) + { + const std::string fileExt = entry.path().extension().string(); + if (fileExt == extension || (extension.size() > 0 && fileExt.size() > 0 && fileExt.substr(1) == extension)) + outputFilenames.push_back(filename); + } + } + } + return false; + } + catch (const fs::filesystem_error& e) + { + msg_error("FileSystem::listDirectory()") << directoryPath << ": " << e.what(); + return true; + } +} + +int FileSystem::findFiles(const std::string& directoryPath, + std::vector& outputFilePaths, + const std::string& extension, const int depth) +{ + try + { + for (const auto& entry : fs::directory_iterator(directoryPath)) + { + const std::string filepath = entry.path().string(); + + if (entry.is_directory() && entry.path().filename().string()[0] != '.' && depth > 0) + { + if (findFiles(filepath, outputFilePaths, extension, depth - 1) == -1) + return -1; + } + else if (entry.is_regular_file()) + { + const std::string filename = entry.path().filename().string(); + if (filename.length() >= extension.length() && + filename.compare(filename.length() - extension.length(), extension.length(), extension) == 0) + { + outputFilePaths.push_back(filepath); + } + } + } + return static_cast(outputFilePaths.size()); + } + catch (const fs::filesystem_error& e) + { + msg_error("FileSystem::findFiles()") << directoryPath << ": " << e.what(); + return -1; + } +} + +void FileSystem::ensureFolderExists(const std::string& pathToFolder) +{ + try + { + if (!fs::exists(pathToFolder)) + { + fs::create_directories(pathToFolder); + } + } + catch (const fs::filesystem_error& e) + { + msg_error("FileSystem::ensureFolderExists()") << pathToFolder << ": " << e.what(); + } +} + +void FileSystem::ensureFolderForFileExists(const std::string& pathToFile) +{ + try + { + fs::path p(pathToFile); + if (p.has_parent_path()) + { + fs::create_directories(p.parent_path()); + } + } + catch (const fs::filesystem_error& e) + { + msg_error("FileSystem::ensureFolderForFileExists()") << pathToFile << ": " << e.what(); } } @@ -551,7 +460,7 @@ bool FileSystem::openFileWithDefaultApplication(const std::string& filename) if (!filename.empty()) { - if (!FileSystem::exists(filename)) + if (!fs::exists(filename)) { msg_error("FileSystem::openFileWithDefaultApplication()") << "File does not exist: " << filename; return success; @@ -561,7 +470,7 @@ bool FileSystem::openFileWithDefaultApplication(const std::string& filename) if ((INT_PTR)ShellExecuteA(nullptr, "open", filename.c_str(), nullptr, nullptr, SW_SHOWNORMAL) > 32) success = true; #elif defined(__APPLE__) - pid_t pid; // points to a buffer that is used to return the process ID of the new child process. + pid_t pid; char* argv[] = {const_cast("open"), const_cast(filename.c_str()), nullptr}; if (posix_spawn(&pid, "/usr/bin/open", nullptr, nullptr, argv, nullptr) == 0) { @@ -570,7 +479,7 @@ bool FileSystem::openFileWithDefaultApplication(const std::string& filename) success = true; } #else - pid_t pid; // points to a buffer that is used to return the process ID of the new child process. + pid_t pid; const char* argv[] = {"xdg-open", filename.c_str(), nullptr}; if (posix_spawn(&pid, "/usr/bin/xdg-open", nullptr, nullptr, const_cast(argv), environ) == 0) { diff --git a/Sofa/framework/Helper/test/system/FileSystem_test.cpp b/Sofa/framework/Helper/test/system/FileSystem_test.cpp index efa3dfd5b0d..66420504718 100644 --- a/Sofa/framework/Helper/test/system/FileSystem_test.cpp +++ b/Sofa/framework/Helper/test/system/FileSystem_test.cpp @@ -44,9 +44,20 @@ static std::string getPath(std::string s) { // { // std::vector fileList; // FileSystem::listDirectory(getPath("empty-directory"), fileList); -// EXPECT_TRUE(fileList.empty()); +// EXPECT_TRUE(fileList.empty()); // } +TEST(FileSystemTest, getExtension) +{ + EXPECT_EQ("d", FileSystem::getExtension("a/b/c.d")); + EXPECT_EQ("txt", FileSystem::getExtension("file.txt")); + EXPECT_EQ("gz", FileSystem::getExtension("archive.tar.gz")); + EXPECT_EQ("", FileSystem::getExtension("no_extension")); + EXPECT_EQ("", FileSystem::getExtension("")); + EXPECT_EQ("h", FileSystem::getExtension("/absolute/path/to/file.h")); + EXPECT_EQ("ext", FileSystem::getExtension(".ext")); +} + TEST(FileSystemTest, listDirectory_nonEmpty) { // required to be able to use EXPECT_MSG_NOEMIT and EXPECT_MSG_EMIT @@ -124,6 +135,51 @@ TEST(FileSystemTest, listDirectory_withExtension_noMatch) EXPECT_TRUE(fileList.empty()); } +TEST(FileSystemTest, findFiles_noDepth) +{ + sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance()); + EXPECT_MSG_NOEMIT(Error); + + std::vector results; + const int count = FileSystem::findFiles(getPath("non-empty-directory"), results, ".txt"); + EXPECT_EQ(count, 2); + EXPECT_EQ(results.size(), 2u); +} + +TEST(FileSystemTest, findFiles_noMatch) +{ + sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance()); + EXPECT_MSG_NOEMIT(Error); + + std::vector results; + const int count = FileSystem::findFiles(getPath("non-empty-directory"), results, ".xyz"); + EXPECT_EQ(count, 0); + EXPECT_TRUE(results.empty()); +} + +TEST(FileSystemTest, findFiles_withDepth) +{ + sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance()); + EXPECT_MSG_NOEMIT(Error); + + // The "mesh" subdirectory contains .obj files, and the root resources dir also has subdirectories + // Use the resources dir with depth to find .txt files in subdirectories + std::vector results; + const int count = FileSystem::findFiles(getPath(""), results, ".txt", 1); + EXPECT_GT(count, 0); + EXPECT_EQ(static_cast(results.size()), count); +} + +TEST(FileSystemTest, findFiles_invalidDirectory) +{ + sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance()); + EXPECT_MSG_EMIT(Error); + + std::vector results; + const int count = FileSystem::findFiles(getPath("thisDirectoryDoesNotExist"), results, ".txt"); + EXPECT_EQ(count, -1); +} + TEST(FileSystemTest, createDirectory) { // required to be able to use EXPECT_MSG_NOEMIT and EXPECT_MSG_EMIT @@ -258,13 +314,6 @@ TEST(FileSystemTest, isAbsolute) EXPECT_TRUE(FileSystem::isAbsolute("D:/abc/def")); } -TEST(FileSystemTest, cleanPath) -{ - EXPECT_EQ("", FileSystem::cleanPath("")); - EXPECT_EQ("/abc/def/ghi/jkl/mno", FileSystem::cleanPath("/abc/def//ghi/jkl///mno")); - EXPECT_EQ("C:/abc/def/ghi/jkl/mno", FileSystem::cleanPath("C:\\abc\\def\\ghi/jkl///mno")); -} - TEST(FileSystemTest, convertBackSlashesToSlashes) { EXPECT_EQ("", FileSystem::convertBackSlashesToSlashes("")); @@ -423,4 +472,82 @@ TEST(FileSystemTest, ensureFolderForFileExists_fileExist) //cleanup EXPECT_TRUE(FileSystem::removeFile(file)); -} \ No newline at end of file +} + +TEST(FileSystemTest, removeFile_existingFile) +{ + const std::string filename = "FileSystemTest_removeFile.txt"; + std::ofstream ofs(filename, std::ofstream::out); + ofs << "test"; + ofs.close(); + + ASSERT_TRUE(FileSystem::exists(filename)); + EXPECT_TRUE(FileSystem::removeFile(filename)); + EXPECT_FALSE(FileSystem::exists(filename)); +} + +TEST(FileSystemTest, removeFile_nonExistingFile) +{ + EXPECT_FALSE(FileSystem::removeFile("FileSystemTest_removeFile_nonExisting.txt")); +} + +TEST(FileSystemTest, removeAll_nonEmptyDirectory) +{ + // Create a directory structure: parent/child/file.txt + const std::string parent = "removeAllTestDir"; + const std::string child = parent + "/subdir"; + EXPECT_FALSE(FileSystem::createDirectory(parent)); + EXPECT_FALSE(FileSystem::createDirectory(child)); + + // Create a file inside + std::ofstream ofs(child + "/file.txt", std::ofstream::out); + ofs << "test"; + ofs.close(); + + ASSERT_TRUE(FileSystem::exists(child + "/file.txt")); + + EXPECT_TRUE(FileSystem::removeAll(parent)); + EXPECT_FALSE(FileSystem::exists(parent)); +} + +TEST(FileSystemTest, removeAll_nonExistingDirectory) +{ + // removeAll on a non-existing path should still return true (no exception thrown by fs::remove_all) + EXPECT_TRUE(FileSystem::removeAll("removeAllTestDir_doesNotExist")); +} + +TEST(FileSystemTest, convertSlashesToBackSlashes) +{ + EXPECT_EQ("", FileSystem::convertSlashesToBackSlashes("")); + EXPECT_EQ("abc\\def\\ghi", FileSystem::convertSlashesToBackSlashes("abc/def/ghi")); + EXPECT_EQ("abc\\def\\ghi", FileSystem::convertSlashesToBackSlashes("abc\\def\\ghi")); + EXPECT_EQ("abc\\def\\ghi", FileSystem::convertSlashesToBackSlashes("abc\\def/ghi")); + EXPECT_EQ("C:\\abc\\def\\ghi", FileSystem::convertSlashesToBackSlashes("C:/abc/def/ghi")); + EXPECT_EQ("C:\\abc\\def\\ghi", FileSystem::convertSlashesToBackSlashes("C:\\abc/def\\ghi")); +} + +TEST(FileSystemTest, removeExtraBackSlashes) +{ + EXPECT_EQ("", FileSystem::removeExtraBackSlashes("")); + EXPECT_EQ("\\", FileSystem::removeExtraBackSlashes("\\")); + EXPECT_EQ("\\", FileSystem::removeExtraBackSlashes("\\\\")); + EXPECT_EQ("\\", FileSystem::removeExtraBackSlashes("\\\\\\")); + EXPECT_EQ("\\", FileSystem::removeExtraBackSlashes("\\\\\\\\")); + EXPECT_EQ("\\abc\\def", FileSystem::removeExtraBackSlashes("\\abc\\def")); + EXPECT_EQ("\\abc\\def\\", FileSystem::removeExtraBackSlashes("\\abc\\def\\")); + EXPECT_EQ("\\abc\\def\\ghi\\jkl\\", FileSystem::removeExtraBackSlashes("\\abc\\\\def\\\\ghi\\jkl\\\\\\")); +} + +TEST(FileSystemTest, cleanPath_slash) +{ + EXPECT_EQ("", FileSystem::cleanPath("")); // assuming FileSystem::SLASH by default + EXPECT_EQ("/abc/def/ghi/jkl/mno", FileSystem::cleanPath("/abc/def//ghi/jkl///mno")); + EXPECT_EQ("C:/abc/def/ghi/jkl/mno", FileSystem::cleanPath("C:\\abc\\def\\ghi/jkl///mno")); +} + +TEST(FileSystemTest, cleanPath_backslash) +{ + EXPECT_EQ("", FileSystem::cleanPath("", FileSystem::BACKSLASH)); + EXPECT_EQ("\\abc\\def\\ghi\\jkl\\mno", FileSystem::cleanPath("\\abc\\def\\\\ghi\\jkl\\\\\\mno", FileSystem::BACKSLASH)); + EXPECT_EQ("C:\\abc\\def\\ghi\\jkl\\mno", FileSystem::cleanPath("C:/abc/def/ghi\\jkl\\\\\\mno", FileSystem::BACKSLASH)); +}