diff --git a/.vscode/settings.json b/.vscode/settings.json index 3ff191a1..18ee967f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,6 +19,15 @@ "inflate_zlib.h": "c", "unity.h": "c", "raiistring.h": "c", - "fileutils.h": "c" + "fileutils.h": "c", + "stat.h": "c", + "windows.h": "c", + "cerrno": "c", + "string.h": "c", + "ctype.h": "c", + "stdbool.h": "c", + "unistd.h": "c", + "bitset": "c", + "ftw.h": "c" }, } \ No newline at end of file diff --git a/Benchmark/BenchmarkDeflateInflateTextFile.c b/Benchmark/BenchmarkDeflateInflateTextFile.c index a2553df4..f36d4e62 100644 --- a/Benchmark/BenchmarkDeflateInflateTextFile.c +++ b/Benchmark/BenchmarkDeflateInflateTextFile.c @@ -1,8 +1,8 @@ #include "b63/b63.h" #include +#include #include #include -#include #include #include #include @@ -24,9 +24,9 @@ B63_BENCHMARK(Deflate_zlib_SmallTextFile, n) { RaiiStringAppend_cString(&pathToSmallBasicTextFileCompressedFile, "SmallBasicTextFile.compressed.txt"); - OpenFile(&pInFile, &pathToSmallBasicTextFile, "r"); - OpenFile(&pOutCompressedFile, &pathToSmallBasicTextFileCompressedFile, - "w"); + OpenFileWithMode(&pInFile, &pathToSmallBasicTextFile, "r"); + OpenFileWithMode(&pOutCompressedFile, + &pathToSmallBasicTextFileCompressedFile, "w"); } const DEFLATE_RETURN_CODES statusDeflate = @@ -59,9 +59,10 @@ B63_BENCHMARK(Inflate_zlib_SmallTextFile, n) { RaiiStringAppend_cString(&pathToSmallBasicTextFileDecompressedFile, "SmallBasicTextFile.decompressed.txt"); - OpenFile(&pCompressedFile, &pathToSmallBasicTextFileCompressed, "r"); - OpenFile(&pOutDecompressedFile, - &pathToSmallBasicTextFileDecompressedFile, "w"); + OpenFileWithMode(&pCompressedFile, &pathToSmallBasicTextFileCompressed, + "r"); + OpenFileWithMode(&pOutDecompressedFile, + &pathToSmallBasicTextFileDecompressedFile, "w"); } const INFLATE_RETURN_CODES statusInflate = diff --git a/Benchmark/CMakeLists.txt b/Benchmark/CMakeLists.txt index b57ce92d..237679e9 100644 --- a/Benchmark/CMakeLists.txt +++ b/Benchmark/CMakeLists.txt @@ -24,4 +24,4 @@ target_link_libraries(BenchmarkDeflateInflateTextFile PRIVATE CoDeLib::CoDeLib) target_link_libraries(BenchmarkDeflateInflateTextFile PRIVATE CoDeLib::Deflate_zlib) target_link_libraries(BenchmarkDeflateInflateTextFile PRIVATE CoDeLib::Inflate_zlib) target_link_libraries(BenchmarkDeflateInflateTextFile PRIVATE CoDeLib::RaiiString) -target_link_libraries(BenchmarkDeflateInflateTextFile PRIVATE CoDeLib::Utility) +target_link_libraries(BenchmarkDeflateInflateTextFile PRIVATE CoDeLib::FileUtils) diff --git a/CoDeLib/CMakeLists.txt b/CoDeLib/CMakeLists.txt index 11ba318f..5a8535a1 100644 --- a/CoDeLib/CMakeLists.txt +++ b/CoDeLib/CMakeLists.txt @@ -24,11 +24,12 @@ install(FILES ${COMMON_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib) add_subdirectory(Deflate_zlib) add_subdirectory(Inflate_zlib) +add_subdirectory(FileUtils) add_subdirectory(RaiiString) add_subdirectory(Test) install( - TARGETS CoDeLib Deflate_zlib Inflate_zlib RaiiString Utility + TARGETS CoDeLib Deflate_zlib Inflate_zlib RaiiString FileUtils EXPORT CoDeLibTargets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/CoDeLib/FileUtils/CMakeLists.txt b/CoDeLib/FileUtils/CMakeLists.txt new file mode 100644 index 00000000..0826fc61 --- /dev/null +++ b/CoDeLib/FileUtils/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(FileUtils STATIC + src/FileUtils.c +) + +target_include_directories(FileUtils PUBLIC + $ + $ +) + +target_link_libraries(FileUtils PUBLIC RaiiString) + +set(FileUtils_PUBLIC_INCLUDE_PATH ${CoDeLib_PUBLIC_INCLUDE_PATH}/FileUtils) +set(FileUtils_PUBLIC_HEADERS + ${FileUtils_PUBLIC_INCLUDE_PATH}/FileUtils.h +) +install(FILES ${FileUtils_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib/FileUtils) + diff --git a/CoDeLib/FileUtils/src/FileUtils.c b/CoDeLib/FileUtils/src/FileUtils.c new file mode 100644 index 00000000..056ee27a --- /dev/null +++ b/CoDeLib/FileUtils/src/FileUtils.c @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include + +#define __USE_XOPEN_EXTENDED 1 +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +//========================= +// Internal function definitions +//========================= + +// Internal function. Must only be called by GetCurrentWorkingDirectory(...) +#ifdef _WIN32 +char *_GetCurrentWorkingDirectory_Windows(char *pFullPath, size_t fullPathSize); +#else +char *_GetCurrentWorkingDirectory_Posix(char *pFullPath, size_t fullPathSize); +#endif + +/*! +@brief Skips the root part of a path and updates the `pPath` pointer to point to +the first character after the root part. This function is platform agnostic. +@param pPath The path to skip the root part of. +@return true if the root part was skipped, false otherwise. +*/ +bool _SkipRootPathOfPath(const char *pPath); + +/*! +@brief Creates a directory. This function is platform agnostic. +@param pPath The path to create. All directories but the last in the path must +exist. +@return true if the directory was created or already exists, false otherwise. +*/ +bool _CreateDir(const char *const pPath); + +/*! +@brief Handles the callback for the nftw function to remove directories and +files. + */ +int _HandleFtwCallback_Remove(const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf); + +//========================= +// Public function implementations +//========================= + +// Based on +// https://nachtimwald.com/2019/07/10/recursive-create-directory-in-c-revisited/ +bool RecursiveMkdir(const char *const pDirname) { + if (pDirname == NULL) { + return false; + } + + const size_t dirnameLength = strlen(pDirname); + if (dirnameLength == 0) { + return false; + } + + char targetPath[MAX_PATH_LENGTH_WTH_TERMINATOR]; + + bool absolutePathSuccess = GetAbsolutePath(pDirname, &targetPath[0], + MAX_PATH_LENGTH_WTH_TERMINATOR); + if (!absolutePathSuccess) { + return false; + } + + bool success = true; + const char separator = '/'; + char *pTargetPath = &targetPath[0]; + const char *pBeginOfTargetPath = pTargetPath; + const size_t targetPathLength = strlen(pTargetPath); + + bool skipRootPath = _SkipRootPathOfPath(pTargetPath); + if (!skipRootPath) { + return false; + } + + char *pPathToCreate = calloc(targetPathLength + 1, sizeof(char)); + if (pPathToCreate == NULL) { + return false; + } + + pTargetPath = strchr(pTargetPath, separator); + while (pTargetPath != NULL) { + /* Skip empty elements. Could be a Windows UNC path or + just multiple separators which is okay. */ + if (pTargetPath != pBeginOfTargetPath && + *(pTargetPath - 1) == separator) { + pTargetPath++; + pTargetPath = strchr(pTargetPath, separator); + continue; + } + // Get the length of the directory name part with the separator and + // without the null terminator. + const size_t dirnamePartLength = + ((pTargetPath - pBeginOfTargetPath) / sizeof(char)) + 1; + if (dirnamePartLength > targetPathLength) { + success = false; + break; + } + // Put the path up to this point into a temporary to pass to the make + // directory function. + memcpy(pPathToCreate, pBeginOfTargetPath, dirnamePartLength); + // Adds null terminator after the seperator + pPathToCreate[dirnamePartLength] = '\0'; + + success = _CreateDir(pPathToCreate); + if (!success) { + break; + } + + pTargetPath++; + pTargetPath = strchr(pTargetPath, separator); + } + + free(pPathToCreate); + pPathToCreate = NULL; + return success; +} + +bool RecursiveRmdir(const char *const pDirname) { + if (pDirname == NULL) { + return false; + } + + const int maxOpenFiles = 64; + const int flags = FTW_DEPTH | FTW_PHYS; + int status = nftw(pDirname, _HandleFtwCallback_Remove, maxOpenFiles, flags); + + return status == 0; +} + +bool IsAbsolutePath(const char *pPath) { + if (pPath == NULL || pPath[0] == '\0') { + return false; + } + + // Windows + if (isalpha(pPath[0]) && pPath[1] == ':') { + return true; + } + + // Linux + else if (pPath[0] == '/') { + return true; + } + + return false; +} + +bool GetAbsolutePath(const char *pPath, char *const pAbsolutePath, + const size_t absolutePathSize) { + if (pPath == NULL || pAbsolutePath == NULL || absolutePathSize == 0) { + return false; + } + + const size_t pathLenght = strnlen(pPath, MAX_PATH_LENGTH_WTH_TERMINATOR); + if (pathLenght == 0 || pathLenght >= MAX_PATH_LENGTH_WTH_TERMINATOR) { + return false; + } + + if (absolutePathSize < (pathLenght + 1)) { + return false; + } + + if (!IsAbsolutePath(pPath)) { + // Get the current working directory and append the dirname to it. + char *currentPath = + GetCurrentWorkingDirectory(&pAbsolutePath[0], absolutePathSize); + if (currentPath == NULL) { + return false; + } + const size_t currentPathLength = strlen(pAbsolutePath); + // (dirnameLength + 1) to include the null terminator + memcpy(&pAbsolutePath[currentPathLength], pPath, + sizeof(char) * (pathLenght + 1)); + } else { + // Copy the absolute path to the target path. + // (dirnameLength + 1) to include the null terminator + memcpy(&pAbsolutePath[0], pPath, sizeof(char) * (pathLenght + 1)); + } + + return true; +} + +char *GetCurrentWorkingDirectory(char *pFullPath, size_t fullPathSize) { + if (pFullPath == NULL || fullPathSize == 0) { + return NULL; + } + + char *pCurrentPath = NULL; + +#ifdef _WIN32 + pCurrentPath = _GetCurrentWorkingDirectory_Windows(pFullPath, fullPathSize); +#else + pCurrentPath = _GetCurrentWorkingDirectory_Posix(pFullPath, fullPathSize); +#endif + + if (pCurrentPath == NULL) { + return NULL; + } + assert((pCurrentPath == pFullPath) && "buffer was reallocated"); + + const size_t currentPathLength = strlen(pFullPath); + if (pFullPath[currentPathLength - 1] != '/') { + if (fullPathSize < currentPathLength + 2) { + return NULL; + } + // Append '/' if not already present + pFullPath[currentPathLength] = '/'; + pFullPath[currentPathLength + 1] = '\0'; + } + + return pFullPath; +} + +bool PathExists(const char *pPath) { + if (pPath == NULL) { + return false; + } + + const size_t pathLenght = strnlen(pPath, MAX_PATH_LENGTH_WTH_TERMINATOR); + if (pathLenght == 0 || pathLenght > MAX_PATH_LENGTH_WTH_TERMINATOR) { + return false; + } + + char targetPath[MAX_PATH_LENGTH_WTH_TERMINATOR]; + + bool absolutePathSuccess = + GetAbsolutePath(pPath, &targetPath[0], MAX_PATH_LENGTH_WTH_TERMINATOR); + if (!absolutePathSuccess) { + return false; + } + + bool pathExists = false; + +#ifdef _WIN32 + const DWORD attributes = GetFileAttributes(&targetPath[0]); + if (attributes != INVALID_FILE_ATTRIBUTES) { + pathExists = true; + } +#else + struct stat statbuf; + const int statResult = stat(&targetPath[0], &statbuf); + if (statResult == 0) { + pathExists = true; + } +#endif + + return pathExists; +} + +void OpenFileWithMode(FILE **pInFile, RaiiString *pFullPath, char *pOpenMode) { + *pInFile = fopen(pFullPath->pString, pOpenMode); + if (*pInFile == NULL) { + printf("Failed to open file: %s\n", pFullPath->pString); + exit(1); + } +} + +size_t GetFileSizeInBytes(FILE *pFile) { + fseek(pFile, 0, SEEK_END); + const size_t fileSize = ftell(pFile); + rewind(pFile); + return fileSize; +} + +bool FilesAreEqual(FILE *pFile1, FILE *pFile2) { + const size_t fileSize1 = GetFileSizeInBytes(pFile1); + const size_t fileSize2 = GetFileSizeInBytes(pFile2); + + if (fileSize1 != fileSize2) { + return false; + } + + const size_t bufferSize = 1024; + char buffer1[bufferSize]; + char buffer2[bufferSize]; + + size_t bytesRead1 = 0; + size_t bytesRead2 = 0; + + do { + bytesRead1 = fread(buffer1, 1, bufferSize, pFile1); + bytesRead2 = fread(buffer2, 1, bufferSize, pFile2); + + if (bytesRead1 != bytesRead2) { + return false; + } + + const int eofFile1 = feof(pFile1); + const int eofFile2 = feof(pFile2); + const int errorFile1 = ferror(pFile1); + const int errorFile2 = ferror(pFile2); + if ((eofFile1 && !eofFile2) || (!eofFile1 && eofFile2)) { + printf("EOF indicator set only by one file\n"); + } else if ((!eofFile1 && errorFile1) || (!eofFile2 && errorFile2)) { + printf("Error indicator set"); + return false; + } + + if (memcmp(buffer1, buffer2, bytesRead1) != 0) { + return false; + } + } while (bytesRead1 > 0); + + return true; +} + +//========================= +// Internal function implementations +//========================= + +#ifdef _WIN32 +// Internal function. Must only be called by GetCurrentWorkingDirectory(...) +char *_GetCurrentWorkingDirectory_Windows(char *pFullPath, + size_t fullPathSize) { + // GetCurrentDirectory() return the size including the null terminator + // if (0, NULL) is provided. + const size_t currentPathLengthWithTerminator = GetCurrentDirectory(0, NULL); + if (currentPathLengthWithTerminator == 0 || + currentPathLengthWithTerminator > fullPathSize) { + return NULL; + } + char *pCurrentWorkingDirectory = + calloc(currentPathLengthWithTerminator, sizeof(char)); + + const size_t numCharsWritten = GetCurrentDirectory( + currentPathLengthWithTerminator, pCurrentWorkingDirectory); + if (numCharsWritten == 0) { + return NULL; + free(pCurrentWorkingDirectory); + pCurrentWorkingDirectory = NULL; + } + + pFullPath = strncpy(pFullPath, pCurrentWorkingDirectory, fullPathSize - 1); + if (pFullPath == NULL) { + free(pCurrentWorkingDirectory); + pCurrentWorkingDirectory = NULL; + return NULL; + } + pFullPath[fullPathSize - 1] = '\0'; + + free(pCurrentWorkingDirectory); + pCurrentWorkingDirectory = NULL; + + return pFullPath; +} +#endif + +#ifndef _WIN32 +// Internal function. Must only be called by GetCurrentWorkingDirectory(...) +char *_GetCurrentWorkingDirectory_Posix(char *pFullPath, size_t fullPathSize) { + return getcwd(pFullPath, fullPathSize); +} +#endif + +bool _SkipRootPathOfPath(const char *pPath) { + if (pPath == NULL) { + return false; + } + + const size_t pathLenght = strnlen(pPath, MAX_PATH_LENGTH_WTH_TERMINATOR); + if (pathLenght == 0 || pathLenght > MAX_PATH_LENGTH_WTH_TERMINATOR) { + return false; + } + +#ifdef _WIN32 + if (pathLenght < 3) { + return false; + } + // Skip Windows drive letter for Windows. + if (isalpha(pPath[0]) && pPath[1] == ':') { + pPath = &pPath[2]; + } else { + // Path if not absolute + return false; + } +#else + if (pathLenght < 2) { + return false; + } + // Skip root separator for non-Windows. + if (pPath[0] == '/') { + pPath = &pPath[1]; + } else { + // Path if not absolute + return false; + } +#endif + + return true; +} + +bool _CreateDir(const char *const pPath) { + if (pPath == NULL) { + return false; + } + +#ifdef _WIN32 + const int creationSuccess = CreateDirectory(pPath, NULL); + if (creationSuccess == FALSE) { + const DWORD lastError = GetLastError(); + if (lastError != ERROR_ALREADY_EXISTS) { + return false; + } + } +#else + const int creationSuccess = mkdir(pPath, 0774); + if (creationSuccess != 0) { + if (errno != EEXIST) { + return false; + } + } +#endif + + return true; +} + +int _HandleFtwCallback_Remove(const char *fpath, + const struct stat *sb __attribute__((unused)), + int typeflag, + struct FTW *ftwbuf __attribute__((unused))) { + if (typeflag == FTW_DP) { + // Remove directory + return rmdir(fpath); + } + + // Remove file + return remove(fpath); +} diff --git a/CoDeLib/Test/CMakeLists.txt b/CoDeLib/Test/CMakeLists.txt index 57e52e61..33204ffc 100644 --- a/CoDeLib/Test/CMakeLists.txt +++ b/CoDeLib/Test/CMakeLists.txt @@ -1,7 +1,8 @@ add_executable(CoDeLib_Test src/main.c src/TestRaiiString.c - src/TestDeflateInflateZlib.c) + src/TestDeflateInflateZlib.c + src/TestFileUtils.c) target_include_directories(CoDeLib_Test PUBLIC $ @@ -10,6 +11,7 @@ target_include_directories(CoDeLib_Test PUBLIC target_link_libraries(CoDeLib_Test PRIVATE Deflate_zlib) target_link_libraries(CoDeLib_Test PRIVATE Inflate_zlib) target_link_libraries(CoDeLib_Test PRIVATE RaiiString) +target_link_libraries(CoDeLib_Test PRIVATE FileUtils) FetchContent_Declare( Unity @@ -22,6 +24,3 @@ FetchContent_Declare( set(UNITY_EXTENSION_FIXTURE ON CACHE INTERNAL "") FetchContent_MakeAvailable(Unity) target_link_libraries(CoDeLib_Test PRIVATE unity) - -add_subdirectory(Utility) -target_link_libraries(CoDeLib_Test PUBLIC Utility) diff --git a/CoDeLib/Test/Utility/CMakeLists.txt b/CoDeLib/Test/Utility/CMakeLists.txt deleted file mode 100644 index bde7d4b7..00000000 --- a/CoDeLib/Test/Utility/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -add_library(Utility STATIC - FileUtils.c -) - -target_include_directories(Utility PUBLIC - $ - $ -) - -target_link_libraries(Utility PUBLIC RaiiString) - -set(Utility_PUBLIC_INCLUDE_PATH ${CoDeLib_PUBLIC_INCLUDE_PATH}/Test/Utility) -set(Utility_PUBLIC_HEADERS - ${Utility_PUBLIC_INCLUDE_PATH}/FileUtils.h -) -install(FILES ${Utility_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib/Test/Utility) - diff --git a/CoDeLib/Test/Utility/FileUtils.c b/CoDeLib/Test/Utility/FileUtils.c deleted file mode 100644 index f255cb02..00000000 --- a/CoDeLib/Test/Utility/FileUtils.c +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include -#include - -void OpenFile(FILE **pInFile, RaiiString *pFullPath, char *pOpenMode) { - *pInFile = fopen(pFullPath->pString, pOpenMode); - if (*pInFile == NULL) { - printf("Failed to open file: %s\n", pFullPath->pString); - exit(1); - } -} - -size_t GetFileSizeInBytes(FILE *pFile) { - fseek(pFile, 0, SEEK_END); - const size_t fileSize = ftell(pFile); - rewind(pFile); - return fileSize; -} - -bool FilesAreEqual(FILE *pFile1, FILE *pFile2) { - const size_t fileSize1 = GetFileSizeInBytes(pFile1); - const size_t fileSize2 = GetFileSizeInBytes(pFile2); - - if (fileSize1 != fileSize2) { - return false; - } - - const size_t bufferSize = 1024; - char buffer1[bufferSize]; - char buffer2[bufferSize]; - - size_t bytesRead1 = 0; - size_t bytesRead2 = 0; - - do { - bytesRead1 = fread(buffer1, 1, bufferSize, pFile1); - bytesRead2 = fread(buffer2, 1, bufferSize, pFile2); - - if (bytesRead1 != bytesRead2) { - return false; - } - - const int eofFile1 = feof(pFile1); - const int eofFile2 = feof(pFile2); - const int errorFile1 = ferror(pFile1); - const int errorFile2 = ferror(pFile2); - if ((eofFile1 && !eofFile2) || (!eofFile1 && eofFile2)) { - printf("EOF indicator set only by one file\n"); - } else if ((!eofFile1 && errorFile1) || (!eofFile2 && errorFile2)) { - printf("Error indicator set"); - return false; - } - - if (memcmp(buffer1, buffer2, bytesRead1) != 0) { - return false; - } - } while (bytesRead1 > 0); - - return true; -} diff --git a/CoDeLib/Test/src/TestDeflateInflateZlib.c b/CoDeLib/Test/src/TestDeflateInflateZlib.c index f5b50084..5822d7e3 100644 --- a/CoDeLib/Test/src/TestDeflateInflateZlib.c +++ b/CoDeLib/Test/src/TestDeflateInflateZlib.c @@ -1,8 +1,8 @@ #include "unity_fixture.h" #include +#include #include #include -#include #include #include #include @@ -43,10 +43,11 @@ TEST(TestDeflateInflateZlib, test_InflateZlibWorkWithDeflateZlib) { RaiiStringAppend_cString(&pathToSmallBasicTextFileDecompressed, "SmallBasicTextFile.decompressed.txt"); - OpenFile(&pInFile, &pathToSmallBasicTextFile, "r"); - OpenFile(&pOutCompressedFile, &pathToSmallBasicTextFileCompressed, "w+"); - OpenFile(&pOutDecompressedFile, &pathToSmallBasicTextFileDecompressed, - "w+"); + OpenFileWithMode(&pInFile, &pathToSmallBasicTextFile, "r"); + OpenFileWithMode(&pOutCompressedFile, &pathToSmallBasicTextFileCompressed, + "w+"); + OpenFileWithMode(&pOutDecompressedFile, + &pathToSmallBasicTextFileDecompressed, "w+"); const DEFLATE_RETURN_CODES statusDeflate = deflate_zlib.Deflate(pInFile, pOutCompressedFile, NULL); diff --git a/CoDeLib/Test/src/TestFileUtils.c b/CoDeLib/Test/src/TestFileUtils.c new file mode 100644 index 00000000..c689f421 --- /dev/null +++ b/CoDeLib/Test/src/TestFileUtils.c @@ -0,0 +1,739 @@ +#include "TestFileUtils.h" +#include "unity_fixture.h" +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +static char *g_pFullPathToBenchmarkTestFiles = NULL; +static char *g_pCurrentWorkingDirectory = NULL; + +void SetupTestFileUtils(char *pFullPathToBenchmarkTestFiles, + char *pCurrentWorkingDirectory) { + g_pFullPathToBenchmarkTestFiles = pFullPathToBenchmarkTestFiles; + g_pCurrentWorkingDirectory = pCurrentWorkingDirectory; +} + +TEST_GROUP(TestFileUtils); + +TEST_SETUP(TestFileUtils) {} + +TEST_TEAR_DOWN(TestFileUtils) { + // TODO: Remove all created directories during tests +} + +//============================== +// IsAbsolutePath(...) +//============================== + +TEST(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsNull) { + bool isAbsolutePath = IsAbsolutePath(NULL); + TEST_ASSERT_FALSE(isAbsolutePath); +} + +TEST(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsEmpty) { + bool isAbsolutePath = IsAbsolutePath(""); + TEST_ASSERT_FALSE(isAbsolutePath); +} + +TEST(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsRelative_Linux) { + TEST_ASSERT_FALSE(IsAbsolutePath("tmp/tmp2/")); + TEST_ASSERT_FALSE(IsAbsolutePath("tmp/tmp2")); + TEST_ASSERT_FALSE(IsAbsolutePath("./tmp/tmp2/")); + TEST_ASSERT_FALSE(IsAbsolutePath("./tmp/tmp2")); +} + +TEST(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsRelative_Windows) { + TEST_ASSERT_FALSE(IsAbsolutePath("tmp\\tmp2\\")); + TEST_ASSERT_FALSE(IsAbsolutePath("tmp\\tmp2")); + TEST_ASSERT_FALSE(IsAbsolutePath(".\\tmp\\tmp2\\")); + TEST_ASSERT_FALSE(IsAbsolutePath(".\\tmp\\tmp2")); +} + +TEST(TestFileUtils, + test_IsAbsolutePath_ReturnsFalseIfPathStartIsInvalid_Windows) { + TEST_ASSERT_FALSE(IsAbsolutePath("\\tmp\\tmp2")); +} + +TEST( + TestFileUtils, + test_IsAbsolutePath_ReturnsFalseIfPathIsNotValidPath_Windows_MultiCharDriveLetter) { + // Note that officialy Windows only supports single character drive letters. + TEST_ASSERT_FALSE(IsAbsolutePath("DE:\\tmp\\tmp2\\")); + TEST_ASSERT_FALSE(IsAbsolutePath("DE:\\tmp\\tmp2")); +} + +TEST(TestFileUtils, test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Linux) { + TEST_ASSERT_TRUE(IsAbsolutePath("/tmp/tmp2/")); + TEST_ASSERT_TRUE(IsAbsolutePath("/tmp/tmp2")); +} + +TEST(TestFileUtils, test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Windows) { + TEST_ASSERT_TRUE(IsAbsolutePath("C:\\tmp\\tmp2\\")); + TEST_ASSERT_TRUE(IsAbsolutePath("C:\\tmp\\tmp2")); +} + +TEST(TestFileUtils, + test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Windows_MixedSeparators) { + TEST_ASSERT_TRUE(IsAbsolutePath("C:\\tmp/tmp2\\")); + TEST_ASSERT_TRUE(IsAbsolutePath("C:\\tmp/tmp2")); + TEST_ASSERT_TRUE(IsAbsolutePath("C:/tmp\\tmp2\\")); + TEST_ASSERT_TRUE(IsAbsolutePath("C:/tmp\\tmp2")); +} + +TEST(TestFileUtils, + test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Linux_MixedSeparators) { + TEST_ASSERT_TRUE(IsAbsolutePath("/tmp\\tmp2/")); + TEST_ASSERT_TRUE(IsAbsolutePath("/tmp\\tmp2")); +} + +//============================== +// GetCurrentWorkingDirectory(...) +//============================== + +TEST(TestFileUtils, test_GetCurrentWorkingDirectory_ReturnsNullIfBufferIsNull) { + char *pCurrentWorkingDirectory = GetCurrentWorkingDirectory(NULL, 0); + TEST_ASSERT_NULL(pCurrentWorkingDirectory); +} + +TEST(TestFileUtils, test_GetCurrentWorkingDirectory_ReturnsNullIfBufferIsZero) { + char localBuffer[10]; + char *pCurrentWorkingDirectory = + GetCurrentWorkingDirectory(&localBuffer[0], 0); + TEST_ASSERT_NULL(pCurrentWorkingDirectory); +} + +TEST(TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsNullIfBufferIsTooSmall) { + + // This length does not include the null terminator. It therefore is too + // small. + const size_t cwdLength = strlen(g_pCurrentWorkingDirectory); + char *pLocalBuffer = calloc(cwdLength, sizeof(char)); + + char *pCurrentWorkingDirectory = + GetCurrentWorkingDirectory(pLocalBuffer, cwdLength); + + TEST_ASSERT_NULL(pCurrentWorkingDirectory); + + free(pLocalBuffer); +} + +TEST(TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsCurrentPathIfBufferCorrectSize) { + + const size_t cwdLength = strlen(g_pCurrentWorkingDirectory); + const size_t allocationLength = cwdLength + 1; + char *pLocalBuffer = calloc(allocationLength, sizeof(char)); + + char *pCurrentWorkingDirectory = + GetCurrentWorkingDirectory(pLocalBuffer, allocationLength); + TEST_ASSERT_EQUAL(pLocalBuffer, pCurrentWorkingDirectory); + TEST_ASSERT_EQUAL_STRING(g_pCurrentWorkingDirectory, + pCurrentWorkingDirectory); + free(pLocalBuffer); +} + +TEST( + TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsCurrentPathIfBufferIsBiggerThanNeeded) { + + const size_t cwdLength = strlen(g_pCurrentWorkingDirectory); + const size_t allocationLength = cwdLength + 1 + 10; + char *pLocalBuffer = calloc(allocationLength, sizeof(char)); + + char *pCurrentWorkingDirectory = + GetCurrentWorkingDirectory(pLocalBuffer, allocationLength); + TEST_ASSERT_EQUAL(pLocalBuffer, pCurrentWorkingDirectory); + TEST_ASSERT_EQUAL_STRING(g_pCurrentWorkingDirectory, + pCurrentWorkingDirectory); + free(pLocalBuffer); +} + +TEST(TestFileUtils, + test_GetCurrentWorkingDirectory_HasSeparatorAtEndIfNotAlreadyPresent) { + + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + GetCurrentWorkingDirectory(localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + const size_t cwdLength = strlen(localBuffer); + TEST_ASSERT_TRUE(localBuffer[cwdLength - 1] == '/'); +} + +//============================== +// GetAbsolutePath(...) +//============================== + +TEST(TestFileUtils, test_GetAbsolutePath_ReturnsFalseIfPathIsNull) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + bool success = + GetAbsolutePath(NULL, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + TEST_ASSERT_FALSE(success); +} + +TEST(TestFileUtils, test_GetAbsolutePath_ReturnsFalseIfBufferIsNull) { + bool success = GetAbsolutePath("tmp/tmp2/", NULL, 10); + TEST_ASSERT_FALSE(success); +} + +TEST(TestFileUtils, test_GetAbsolutePath_ReturnsFalseIfBufferSizeIsZero) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + bool success = GetAbsolutePath("tmp/tmp2/", localBuffer, 0); + TEST_ASSERT_FALSE(success); +} + +TEST(TestFileUtils, + test_GetAbsolutePath_ReturnsFalseIfBufferSizeIsLessThenPathSize) { + char *pPath = "tmp/tmp2/"; + const size_t pathLength = strlen(pPath); + char localBuffer[pathLength]; + bool success = GetAbsolutePath(pPath, localBuffer, pathLength); + TEST_ASSERT_FALSE(success); +} + +TEST(TestFileUtils, + test_GetAbsolutePath_AllowsBuffersToBeBiggerThanMaxPathLength) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR + 1]; + bool success = GetAbsolutePath("tmp/tmp2/", localBuffer, + MAX_PATH_LENGTH_WTH_TERMINATOR + 1); + TEST_ASSERT_TRUE(success); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_WindowsStyle) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "C:\\a\\b\\"; + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); + TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_WindowsStyle) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "C:\\a\\b"; + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); + TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtention_WindowsStyle) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "C:\\a\\b.txt"; + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); + TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyle) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "/a/b/"; + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); + TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_PosixStyle) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "/a/b"; + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); + TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtentions_PosixStyle) { + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "/a/b.txt"; + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]); + TEST_ASSERT_EQUAL_STRING(pPath, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_WindowsStyle) { + char expectedAbsolutePathBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + GetCurrentWorkingDirectory(expectedAbsolutePathBuffer, + MAX_PATH_LENGTH_WTH_TERMINATOR); + RAII_STRING expectedAbsolutePath = + RaiiStringCreateFromCString(expectedAbsolutePathBuffer); + + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "b\\"; + + RaiiStringAppend_cString(&expectedAbsolutePath, pPath); + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_GREATER_THAN(strlen(pPath), strlen(&localBuffer[0])); + TEST_ASSERT_EQUAL_STRING(expectedAbsolutePath.pString, localBuffer); +} + +TEST( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_PosixStyle) { + char expectedAbsolutePathBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + GetCurrentWorkingDirectory(expectedAbsolutePathBuffer, + MAX_PATH_LENGTH_WTH_TERMINATOR); + RAII_STRING expectedAbsolutePath = + RaiiStringCreateFromCString(expectedAbsolutePathBuffer); + + char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + char *pPath = "b/"; + + RaiiStringAppend_cString(&expectedAbsolutePath, pPath); + + bool success = + GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR); + + TEST_ASSERT_TRUE(success); + TEST_ASSERT_GREATER_THAN(strlen(pPath), strlen(&localBuffer[0])); + TEST_ASSERT_EQUAL_STRING(expectedAbsolutePath.pString, localBuffer); +} + +//============================== +// PathExists(...) +//============================== + +TEST(TestFileUtils, test_PathExists_ReturnsFalseIfPathIsNull) { + bool exists = PathExists(NULL); + TEST_ASSERT_FALSE(exists); +} + +TEST(TestFileUtils, test_PathExists_ReturnsFalseIfPathIsEmpty) { + bool exists = PathExists(""); + TEST_ASSERT_FALSE(exists); +} + +TEST(TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathDoesNotExist_WindowsStyle) { + bool exists = PathExists("C:\\NonExistentPath\\"); + TEST_ASSERT_FALSE(exists); +} + +TEST(TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathDoesNotExist_PosixStyle) { + bool exists = PathExists("/NonExistentPath/"); + TEST_ASSERT_FALSE(exists); +} + +TEST(TestFileUtils, + test_PathExists_ReturnsTrueIfAbsolutePathToDirectoryExists) { + bool exists = PathExists(g_pCurrentWorkingDirectory); + TEST_ASSERT_TRUE(exists); +} + +TEST(TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathToDirectoryDoesNotExist) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/NonExistentPath/"); + + bool exists = PathExists(tmpPath.pString); + TEST_ASSERT_FALSE(exists); +} + +TEST(TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathToFileDoesNotExist) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/NonExistentFile.txt"); + + bool exists = PathExists(tmpPath.pString); + TEST_ASSERT_FALSE(exists); +} + +TEST(TestFileUtils, + test_PathExists_ReturnsTrueIfRelativePathToDirectoryExists) { + bool exists = PathExists("./"); + TEST_ASSERT_TRUE(exists); +} + +TEST(TestFileUtils, test_PathExists_ReturnsTrueIfAbsolutePathToFileExists) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "SmallBasicTextFile.txt"); + + bool exists = PathExists(tmpPath.pString); + TEST_ASSERT_TRUE(exists); +} + +//============================== +// RecursiveMkdir(...) +//============================== + +TEST(TestFileUtils, test_RecursiveMkdir_ReturnsFalseIfDirnameIsNull) { + bool success = RecursiveMkdir(NULL); + TEST_ASSERT_FALSE(success); +} + +TEST(TestFileUtils, test_RecursiveMkdir_CanCreateAbsolutePath) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/tmp/"); + + bool success = RecursiveMkdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_TRUE(PathExists(tmpPath.pString)); + + TEST_ASSERT_TRUE(RecursiveRmdir(tmpPath.pString)); +} + +TEST(TestFileUtils, test_RecursiveMkdir_CanCreateRecursiveAbsolutePath) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/tmp2/"); + RAII_STRING tmpSubPath = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath, "Other/"); + + bool success = RecursiveMkdir(tmpSubPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_TRUE(PathExists(tmpSubPath.pString)); + + TEST_ASSERT_TRUE(RecursiveRmdir(tmpPath.pString)); +} + +TEST(TestFileUtils, test_RecursiveMkdir_CanCreateRelativePath) { + const char *pDirname = "./tmp/"; + bool success = RecursiveMkdir(pDirname); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_TRUE(PathExists(pDirname)); + + TEST_ASSERT_TRUE(RecursiveRmdir(pDirname)); +} + +TEST(TestFileUtils, test_RecursiveMkdir_CanCreateRecursiveRelativePath) { + const char *pDirname = "./tmp2/"; + RAII_STRING tmpPath = RaiiStringCreateFromCString(pDirname); + RAII_STRING tmpSubPath = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath, "Other/"); + + bool success = RecursiveMkdir(tmpSubPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_TRUE(PathExists(tmpSubPath.pString)); + + TEST_ASSERT_TRUE(RecursiveRmdir(tmpPath.pString)); +} + +//============================== +// RecursiveRmdir(...) +//============================== + +TEST(TestFileUtils, test_RecursiveRmdir_ReturnsFalseIfDirnameIsNull) { + bool success = RecursiveRmdir(NULL); + TEST_ASSERT_FALSE(success); +} + +TEST(TestFileUtils, test_RecursiveRmdir_CanDeleteAbsolutePath) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/tmp/"); + + bool success = RecursiveMkdir(tmpPath.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory."); + } + success = RecursiveRmdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(tmpPath.pString)); +} + +TEST(TestFileUtils, test_RecursiveRmdir_CanDeleteRecursiveAbsolutePath) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/tmp2/"); + RAII_STRING tmpSubPath = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath, "Other/"); + + bool success = RecursiveMkdir(tmpSubPath.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory."); + } + success = RecursiveRmdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(tmpPath.pString)); +} + +TEST(TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveAbsolutePathWithFile) { + RAII_STRING tmpPath = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&tmpPath, "/tmp2/"); + RAII_STRING tmpSubPath = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath, "Other/"); + + bool success = RecursiveMkdir(tmpSubPath.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory."); + } + + // Make a file in the directory + RaiiStringAppend_cString(&tmpSubPath, "file.txt"); + FILE *pFile = fopen(tmpSubPath.pString, "w"); + if (pFile == NULL) { + TEST_FAIL_MESSAGE("Failed to create file."); + } + fclose(pFile); + + success = RecursiveRmdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(tmpPath.pString)); +} + +TEST(TestFileUtils, test_RecursiveRmdir_CanDeleteRelativePath) { + const char *pDirname = "./tmp/"; + bool success = RecursiveMkdir(pDirname); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory."); + } + success = RecursiveRmdir(pDirname); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(pDirname)); +} + +TEST(TestFileUtils, test_RecursiveRmdir_CanDeleteRecursiveRelativePath) { + RAII_STRING tmpPath = RaiiStringCreateFromCString("./tmp/"); + RAII_STRING tmpSubPath = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath, "Other/"); + + bool success = RecursiveMkdir(tmpSubPath.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory."); + } + + success = RecursiveRmdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(tmpPath.pString)); +} + +TEST( + TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveRelativePathWithMultipleDirectories) { + RAII_STRING tmpPath = RaiiStringCreateFromCString("./tmp/"); + RAII_STRING tmpSubPath1 = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath1, "Other1/"); + RAII_STRING tmpSubPath2 = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath2, "Other2/"); + + bool success = RecursiveMkdir(tmpSubPath1.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory 1."); + } + success = RecursiveMkdir(tmpSubPath2.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory 2."); + } + + success = RecursiveRmdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(tmpPath.pString)); +} + +TEST(TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveRelativePathWithFile) { + RAII_STRING tmpPath = RaiiStringCreateFromCString("./tmp/"); + RAII_STRING tmpSubPath = RaiiStringCreateFromCString(tmpPath.pString); + RaiiStringAppend_cString(&tmpSubPath, "Other/"); + + bool success = RecursiveMkdir(tmpSubPath.pString); + if (!success) { + TEST_FAIL_MESSAGE("Failed to create directory."); + } + + // Make a file in the directory + RaiiStringAppend_cString(&tmpSubPath, "file.txt"); + FILE *pFile = fopen(tmpSubPath.pString, "w"); + if (pFile == NULL) { + TEST_FAIL_MESSAGE("Failed to create file."); + } + fclose(pFile); + + success = RecursiveRmdir(tmpPath.pString); + TEST_ASSERT_TRUE(success); + + TEST_ASSERT_FALSE(PathExists(tmpPath.pString)); +} + +//============================== +// TEST_GROUP_RUNNER +//============================== + +TEST_GROUP_RUNNER(TestFileUtils) { + // IsAbsolutePath(...) + RUN_TEST_CASE(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsNull); + RUN_TEST_CASE(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsEmpty); + RUN_TEST_CASE(TestFileUtils, + test_IsAbsolutePath_ReturnsFalseIfPathIsRelative_Linux); + RUN_TEST_CASE(TestFileUtils, + test_IsAbsolutePath_ReturnsFalseIfPathIsRelative_Windows); + RUN_TEST_CASE(TestFileUtils, + test_IsAbsolutePath_ReturnsFalseIfPathStartIsInvalid_Windows); + RUN_TEST_CASE( + TestFileUtils, + test_IsAbsolutePath_ReturnsFalseIfPathIsNotValidPath_Windows_MultiCharDriveLetter); + RUN_TEST_CASE(TestFileUtils, + test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Linux); + RUN_TEST_CASE(TestFileUtils, + test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Windows); + RUN_TEST_CASE( + TestFileUtils, + test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Windows_MixedSeparators); + RUN_TEST_CASE( + TestFileUtils, + test_IsAbsolutePath_ReturnsTrueIfPathIsAbsolute_Linux_MixedSeparators); + + // GetCurrentWorkingDirectory(...) + RUN_TEST_CASE(TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsNullIfBufferIsNull); + RUN_TEST_CASE(TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsNullIfBufferIsZero); + RUN_TEST_CASE( + TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsNullIfBufferIsTooSmall); + RUN_TEST_CASE( + TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsCurrentPathIfBufferCorrectSize); + RUN_TEST_CASE( + TestFileUtils, + test_GetCurrentWorkingDirectory_ReturnsCurrentPathIfBufferIsBiggerThanNeeded); + RUN_TEST_CASE( + TestFileUtils, + test_GetCurrentWorkingDirectory_HasSeparatorAtEndIfNotAlreadyPresent); + + // GetAbsolutePath(...) + RUN_TEST_CASE(TestFileUtils, test_GetAbsolutePath_ReturnsFalseIfPathIsNull); + RUN_TEST_CASE(TestFileUtils, + test_GetAbsolutePath_ReturnsFalseIfBufferIsNull); + RUN_TEST_CASE(TestFileUtils, + test_GetAbsolutePath_ReturnsFalseIfBufferSizeIsZero); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsFalseIfBufferSizeIsLessThenPathSize); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_AllowsBuffersToBeBiggerThanMaxPathLength); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_WindowsStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_WindowsStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtention_WindowsStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_PosixStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtentions_PosixStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_WindowsStyle); + RUN_TEST_CASE( + TestFileUtils, + test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_PosixStyle); + + // PathExists(...) + RUN_TEST_CASE(TestFileUtils, test_PathExists_ReturnsFalseIfPathIsNull); + RUN_TEST_CASE(TestFileUtils, test_PathExists_ReturnsFalseIfPathIsEmpty); + RUN_TEST_CASE( + TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathDoesNotExist_WindowsStyle); + RUN_TEST_CASE( + TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathDoesNotExist_PosixStyle); + RUN_TEST_CASE(TestFileUtils, + test_PathExists_ReturnsTrueIfAbsolutePathToDirectoryExists); + RUN_TEST_CASE( + TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathToDirectoryDoesNotExist); + RUN_TEST_CASE(TestFileUtils, + test_PathExists_ReturnsFalseIfAbsolutePathToFileDoesNotExist); + RUN_TEST_CASE(TestFileUtils, + test_PathExists_ReturnsTrueIfRelativePathToDirectoryExists); + RUN_TEST_CASE(TestFileUtils, + test_PathExists_ReturnsTrueIfAbsolutePathToFileExists); + + // RecursiveMkdir(...) + RUN_TEST_CASE(TestFileUtils, + test_RecursiveMkdir_ReturnsFalseIfDirnameIsNull); + RUN_TEST_CASE(TestFileUtils, test_RecursiveMkdir_CanCreateAbsolutePath); + RUN_TEST_CASE(TestFileUtils, + test_RecursiveMkdir_CanCreateRecursiveAbsolutePath); + RUN_TEST_CASE(TestFileUtils, test_RecursiveMkdir_CanCreateRelativePath); + RUN_TEST_CASE(TestFileUtils, + test_RecursiveMkdir_CanCreateRecursiveRelativePath); + + // RecursiveRmdir(...) + RUN_TEST_CASE(TestFileUtils, + test_RecursiveRmdir_ReturnsFalseIfDirnameIsNull); + RUN_TEST_CASE(TestFileUtils, test_RecursiveRmdir_CanDeleteAbsolutePath); + RUN_TEST_CASE(TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveAbsolutePath); + RUN_TEST_CASE(TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveAbsolutePathWithFile); + RUN_TEST_CASE(TestFileUtils, test_RecursiveRmdir_CanDeleteRelativePath); + RUN_TEST_CASE(TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveRelativePath); + RUN_TEST_CASE( + TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveRelativePathWithMultipleDirectories); + RUN_TEST_CASE(TestFileUtils, + test_RecursiveRmdir_CanDeleteRecursiveRelativePathWithFile); +} diff --git a/CoDeLib/Test/src/TestFileUtils.h b/CoDeLib/Test/src/TestFileUtils.h new file mode 100644 index 00000000..c179ad84 --- /dev/null +++ b/CoDeLib/Test/src/TestFileUtils.h @@ -0,0 +1,4 @@ +#pragma once + +void SetupTestFileUtils(char *pFullPathToBenchmarkTestFiles, + char *pCurrentWorkingDirectory); diff --git a/CoDeLib/Test/src/main.c b/CoDeLib/Test/src/main.c index c5068e81..fce95116 100644 --- a/CoDeLib/Test/src/main.c +++ b/CoDeLib/Test/src/main.c @@ -1,25 +1,33 @@ #include "unity_fixture.h" #include "TestDeflateInflateZlib.h" -#include +#include "TestFileUtils.h" +#include static void RunAllTests(void) { RUN_TEST_GROUP(TestRaiiString); RUN_TEST_GROUP(TestDeflateInflateZlib); + RUN_TEST_GROUP(TestFileUtils); } int main(int argc, const char **argv) { RAII_STRING fullPathToBenchmarkTestFiles; + // Should end with '/' + RAII_STRING currentWorkingDirectory; + // TODO: use getopt(...) - if (argc == 1) { - printf("No arguments provided\n"); + if (argc < 3) { + printf("Not enough arguments provided\n"); return 1; } else { fullPathToBenchmarkTestFiles = RaiiStringCreateFromCString(argv[1]); + currentWorkingDirectory = RaiiStringCreateFromCString(argv[2]); } SetupTestDeflateInflateZlib(fullPathToBenchmarkTestFiles.pString); + SetupTestFileUtils(fullPathToBenchmarkTestFiles.pString, + currentWorkingDirectory.pString); return UnityMain(argc, argv, RunAllTests); } diff --git a/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h b/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h new file mode 100644 index 00000000..df1ebb2b --- /dev/null +++ b/CoDeLib/include/CoDeLib/FileUtils/FileUtils.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#define MAX_PATH_LENGTH 256 +#define MAX_PATH_LENGTH_WTH_TERMINATOR (MAX_PATH_LENGTH + 1) + +/*! +@brief Recursively creates a directory. If the directory already exists, nothing +happens. +@param pDirname The directory to create. The directory needs to end in '/'. +@return true if the directory was created or already exists, false otherwise. +*/ +bool RecursiveMkdir(const char *const pDirname); + +/*! +@brief Recursively deletes a directory and all files. If the directory does not +exist, nothing happens. +@param pDirname The directory to delete. The directory needs to end in '/'. +@return true if the directory was deleted or did not exist, false otherwise. +*/ +bool RecursiveRmdir(const char *const pDirname); + +/*! +@brief Checks if a path is absolute. This function is platform agnostic. +@param pPath The path to check. +@return true if the path is absolute, false otherwise. +*/ +bool IsAbsolutePath(const char *pPath); + +/*! +@brief Gets the absolute path of a path. If pPath is already an absolute path, +pAbsolutePath will be a copy of this string . This function is platform +agnostic. +@param pPath The path to get the absolute path of. +@param pAbsolutePath The buffer to place the absolute path in. +@param absolutePathSize The total size of the buffer. +@return true if the absolute path was successfully placed in pAbsolutePath, +false otherwise. +*/ +bool GetAbsolutePath(const char *pPath, char *const pAbsolutePath, + const size_t absolutePathSize); + +/*! +@brief Gets the current working directory and places it in pFullPath. If +fullPathLength is too small, the function will return NULL and nothing will be +placed in pFullPath. The path will appended with a '/' if not already present. +@param pFullPath The buffer to place the current working directory in. +@param fullPathLength The total size of the buffer. +@return pFullPath if successful, NULL otherwise. +*/ +char *GetCurrentWorkingDirectory(char *pFullPath, size_t fullPathSize); + +/*! +@brief Checks if a file or directory exists. +@param pPath The path to check. Can be a relative of absolute path. Can end in +either a directory or a file. A path to a directory is assumed to end in a '/'. +@return true if the file or directory exists, false otherwise. +*/ +bool PathExists(const char *pPath); + +void OpenFileWithMode(FILE **pInFile, RaiiString *pFullPath, char *pOpenMode); +size_t GetFileSizeInBytes(FILE *pFile); +bool FilesAreEqual(FILE *pFile1, FILE *pFile2); diff --git a/CoDeLib/include/CoDeLib/Test/Utility/FileUtils.h b/CoDeLib/include/CoDeLib/Test/Utility/FileUtils.h deleted file mode 100644 index 4e0758ac..00000000 --- a/CoDeLib/include/CoDeLib/Test/Utility/FileUtils.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include -#include -#include - -void OpenFile(FILE **pInFile, RaiiString *pFullPath, char *pOpenMode); -size_t GetFileSizeInBytes(FILE *pFile); -bool FilesAreEqual(FILE *pFile1, FILE *pFile2); diff --git a/Scripts/RunTest.py b/Scripts/RunTest.py index 20104009..cb0a58a4 100644 --- a/Scripts/RunTest.py +++ b/Scripts/RunTest.py @@ -34,6 +34,10 @@ CurrentScriptPath = Path(os.path.dirname(os.path.abspath(__file__))) RepositoryRootPath = Path(CurrentScriptPath.parent) BenchmarkTestFilesPath = Path(RepositoryRootPath / "Benchmark" / "Files") +CurrentWorkingDirectory = os.getcwd() + +# Add trailing slash to the path +CurrentWorkingDirectory = CurrentWorkingDirectory + "/" os.chdir(RepositoryRootPath) @@ -112,9 +116,19 @@ def RunTests( TestExecutablePath.chmod(TestExecutablePath.stat().st_mode | stat.S_IEXEC) - TestOptions = '"' + str(BenchmarkTestFilesPath) + '/"' + " -v" + TestOptions = ( + '"' + + str(BenchmarkTestFilesPath) + + '/"' + + ' "' + + CurrentWorkingDirectory + + '" ' + + "-v" + ) TestCommand = str(TestExecutablePath) + " " + TestOptions + print("command: {}\n".format(TestCommand)) + RawTestResultsFileNames.append(GetTestResultsFileRawName(buildConfig, testName)) TestResultsFileRaw = open(RawTestResultsFileNames[-1], "w")