From 9c4650d7bc7480e716f0771b02f325d953986c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6nkk=C3=B6?= Date: Sun, 3 Sep 2023 20:26:05 +0300 Subject: [PATCH] Refactor tests --- .gitignore | 4 - CMakeLists.txt | 1 + include/dirent.h | 46 +++-- tests/t-compile.c | 37 +++- tests/t-cplusplus.cpp | 29 ++- tests/t-dirent.c | 90 ++++----- tests/t-scandir.c | 414 +++++++++++++++++++++++------------------- tests/t-strverscmp.c | 97 ++++++---- tests/t-symlink.c | 318 ++++++++++++++++++++++++++++---- tests/t-telldir.c | 20 +- tests/t-unicode.c | 325 +++++++++++++++++++-------------- tests/t-utf8.c | 60 ++++-- 12 files changed, 926 insertions(+), 515 deletions(-) diff --git a/.gitignore b/.gitignore index ad28b25..c89e826 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,3 @@ /*.filters /*.vcxproj /*.dir - -# Some filesystems do not support symlinks, therefore -# it is a bad idea to include symlinks in a git source tree -/tests/1/link_* diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a861b4..69e5f34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ if(DIRENT_BUILD_TESTS) add_executable(${TEST_NAME} EXCLUDE_FROM_ALL ${ARGN}) add_test(NAME ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND $) add_dependencies(check ${TEST_NAME}) + set_property(TEST ${TEST_NAME} PROPERTY SKIP_RETURN_CODE 77) endfunction(add_test_executable) add_test_executable(t-compile tests/t-compile.c) diff --git a/include/dirent.h b/include/dirent.h index 7056003..fe99484 100644 --- a/include/dirent.h +++ b/include/dirent.h @@ -99,13 +99,12 @@ # define S_IFBLK 0 #endif -/* Link - * S_IFLNK is not defined in Windows's `stat.h`, so we define it here. - * In Windows's `stat.h`, file type macros (S_IFDIR, S_IFREG...) are defined - * bitmasks, except that they are NOT on Linux. So long as S_* & ~S_IFMT == 0, - * as is the case in Linux, the S_IS*(mode) macros works fine. */ +/* + * Symbolic link. Be ware that S_IFLNK value and S_ISLNK() macro are only + * usable with dirent - they do not work with stat() function call! + */ #if !defined(S_IFLNK) -# define S_IFLNK (S_IFMT & 0XCCCCCCCC) +# define S_IFLNK (_S_IFDIR | _S_IFREG) #endif /* Socket */ @@ -128,6 +127,11 @@ # define S_IXUSR 0 #endif +/* User full permissions */ +#if !defined(S_IRWXU) +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#endif + /* Read group permission */ #if !defined(S_IRGRP) # define S_IRGRP 0 @@ -143,6 +147,11 @@ # define S_IXGRP 0 #endif +/* Group full permissions */ +#if !defined(S_IRWXG) +# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +#endif + /* Read others permission */ #if !defined(S_IROTH) # define S_IROTH 0 @@ -158,6 +167,11 @@ # define S_IXOTH 0 #endif +/* Other full permissions */ +#if !defined(S_IRWXO) +# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#endif + /* Maximum length of file name */ #if !defined(PATH_MAX) # define PATH_MAX MAX_PATH @@ -184,10 +198,10 @@ #define DTTOIF(type) (type) /* - * File type macros. Note that block devices, sockets and links cannot be - * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are - * only defined for compatibility. These macros should always return false - * on Windows. + * File type macros. Note that block devices and sockets cannot be + * distinguished on Windows, and the macros S_ISBLK and S_ISSOCK are only + * defined for compatibility. These macros should always return false on + * Windows. */ #if !defined(S_ISFIFO) # define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) @@ -531,12 +545,8 @@ _wreaddir_r( DWORD attr = datap->dwFileAttributes; if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) entry->d_type = DT_CHR; -#ifdef FILE_ATTRIBUTE_REPARSE_POINT - /* A Windows link to directory is both symlink (reparse point) and - * directory. Symlink takes precedence, just as Linux does. */ - else if ((attr & FILE_ATTRIBUTE_REPARSE_POINT)) + else if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) entry->d_type = DT_LNK; -#endif else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) entry->d_type = DT_DIR; else @@ -797,12 +807,8 @@ readdir_r( DWORD attr = datap->dwFileAttributes; if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) entry->d_type = DT_CHR; -#ifdef FILE_ATTRIBUTE_REPARSE_POINT - /* A Windows link to directory is both symlink (reparse point) and - * directory. Symlink takes precedence, just as Linux does. */ - else if ((attr & FILE_ATTRIBUTE_REPARSE_POINT)) + else if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) entry->d_type = DT_LNK; -#endif else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) entry->d_type = DT_DIR; else diff --git a/tests/t-compile.c b/tests/t-compile.c index 90d8e50..1a24746 100644 --- a/tests/t-compile.c +++ b/tests/t-compile.c @@ -15,14 +15,26 @@ #include #include +static void test_properties(void); +static void test_length(void); +static void initialize(void); +static void cleanup(void); + int -main(int argc, char *argv[]) +main(void) { - struct dirent *dirp = NULL; + initialize(); - (void) argc; - (void) argv; + test_properties(); + test_length(); + + cleanup(); + return EXIT_SUCCESS; +} +static void +test_properties(void) +{ #ifdef _DIRENT_HAVE_D_TYPE printf("Has d_type\n"); #endif @@ -38,9 +50,24 @@ main(int argc, char *argv[]) #ifdef _D_ALLOC_NAMLEN printf("Has _D_ALLOC_NAMLEN\n"); #endif +} + +static void +test_length(void) +{ + struct dirent *dirp = NULL; printf("Length of d_name with terminator: %d\n", (int) sizeof(dirp->d_name)); +} + +static void +initialize(void) +{ + /*NOP*/; +} +static void +cleanup(void) +{ printf("OK\n"); - return EXIT_SUCCESS; } diff --git a/tests/t-cplusplus.cpp b/tests/t-cplusplus.cpp index f37d71f..bff3c7e 100644 --- a/tests/t-cplusplus.cpp +++ b/tests/t-cplusplus.cpp @@ -16,17 +16,18 @@ using namespace std; static int only_readme(const struct dirent *entry); static void test_retrieval(void); static void test_scan(void); +static void initialize(void); +static void cleanup(void); int -main(int argc, char *argv[]) +main(void) { - (void) argc; - (void) argv; + initialize(); test_retrieval(); test_scan(); - cout << "OK" << endl; + cleanup(); return EXIT_SUCCESS; } @@ -106,12 +107,8 @@ test_retrieval(void) assert(_D_ALLOC_NAMLEN(ent) > 3); #endif found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (ent->d_type != DT_LNK || ent->d_name[0] != 'l') { -#else - } else if (ent->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ cerr << "Unexpected file " << ent->d_name << endl; abort(); } @@ -157,3 +154,15 @@ only_readme(const struct dirent *entry) return pass; } + +static void +initialize(void) +{ + /*NOP*/; +} + +static void +cleanup(void) +{ + cout << "OK" << endl; +} diff --git a/tests/t-dirent.c b/tests/t-dirent.c index 566038f..ef131a0 100644 --- a/tests/t-dirent.c +++ b/tests/t-dirent.c @@ -32,14 +32,14 @@ static void test_chdir(void); static void test_filename(void); static void test_readdir(void); static void test_wreaddir(void); +static void initialize(void); +static void cleanup(void); int -main(int argc, char *argv[]) +main(void) { - (void) argc; - (void) argv; + initialize(); - /* Execute tests */ test_macros(); test_retrieval(); test_nonexistent(); @@ -51,7 +51,7 @@ main(int argc, char *argv[]) test_readdir(); test_wreaddir(); - printf("OK\n"); + cleanup(); return EXIT_SUCCESS; } @@ -150,12 +150,8 @@ test_retrieval(void) assert(_D_ALLOC_NAMLEN(ent) > 3); #endif found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (ent->d_type != DT_LNK || ent->d_name[0] != 'l') { -#else - } else if (ent->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file %s\n", ent->d_name); abort(); } @@ -219,12 +215,8 @@ test_rewind(void) } else if (strcmp(ent->d_name, "dir") == 0) { /* Just a directory */ found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (ent->d_type != DT_LNK || ent->d_name[0] != 'l') { -#else - } else if (ent->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file %s\n", ent->d_name); abort(); } @@ -252,12 +244,8 @@ test_rewind(void) } else if (strcmp(ent->d_name, "dir") == 0) { /* Just a directory */ found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (ent->d_type != DT_LNK || ent->d_name[0] != 'l') { -#else - } else if (ent->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file %s\n", ent->d_name); abort(); } @@ -294,12 +282,8 @@ test_chdir(void) } else if (strcmp(ent->d_name, "dir") == 0) { /* Just a directory */ found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (ent->d_type != DT_LNK || ent->d_name[0] != 'l') { -#else - } else if (ent->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file %s\n", ent->d_name); abort(); } @@ -332,12 +316,8 @@ test_chdir(void) } else if (strcmp(ent->d_name, "dir") == 0) { /* Just a directory */ found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (ent->d_type != DT_LNK || ent->d_name[0] != 'l') { -#else - } else if (ent->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file %s\n", ent->d_name); abort(); } @@ -436,9 +416,9 @@ test_readdir(void) size_t n = 0; while (n < 10 && readdir_r(dir, &ent[n], &entry) == /*OK*/0 && entry != NULL) n++; - /* Make sure that we got all the files from directory, - * with or without the two symlinks created by `t-symlink` */ - assert(n >= 4 && n <= 6); + + /* Make sure that we got all the files from directory */ + assert(n == 4); /* Check entries in memory */ int found = 0; @@ -506,12 +486,8 @@ test_readdir(void) assert(_D_ALLOC_NAMLEN(entry) > 3); #endif found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (entry->d_type != DT_LNK || entry->d_name[0] != 'l') { -#else - } else if (entry->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file %s\n", entry->d_name); abort(); } @@ -543,9 +519,9 @@ test_wreaddir(void) size_t n = 0; while (n < 10 && _wreaddir_r(dir, &ent[n], &entry) == /*OK*/0 && entry != NULL) n++; - /* Make sure that we got all the files from directory, - * with or without the two symlinks created by `t-symlink` */ - assert(n >= 4 && n <= 6); + + /* Make sure that we got all the files from directory */ + assert(n == 4); /* Check entries in memory */ int found = 0; @@ -613,12 +589,8 @@ test_wreaddir(void) assert(_D_ALLOC_NAMLEN(entry) > 3); #endif found += 8; -#ifdef _DIRENT_HAVE_D_TYPE - } else if (entry->d_type != DT_LNK || entry->d_name[0] != 'l') { -#else - } else if (entry->d_name[0] != 'l') { -#endif - /* Other file. Symlinks created by t-symlink are ignored. */ + } else { + /* Other file */ fprintf(stderr, "Unexpected file\n"); abort(); } @@ -630,3 +602,15 @@ test_wreaddir(void) _wclosedir(dir); #endif } + +static void +initialize(void) +{ + /*NOP*/; +} + +static void +cleanup(void) +{ + printf("OK\n"); +} diff --git a/tests/t-scandir.c b/tests/t-scandir.c index c463f9b..79f284c 100644 --- a/tests/t-scandir.c +++ b/tests/t-scandir.c @@ -25,233 +25,254 @@ #undef NDEBUG #include -/* Filter and sort functions */ +static void test_filter(void); +static void test_sort(void); +static void test_custom(void); +static void test_enoent(void); +static void test_enotdir(void); +static void test_versionsort(void); +static void test_large(void); static int only_readme(const struct dirent *entry); static int no_directories(const struct dirent *entry); static int reverse_alpha(const struct dirent **a, const struct dirent **b); +static void initialize(void); +static void cleanup(void); int -main(int argc, char *argv[]) +main(void) { - struct dirent **files; - int i; - int n; + initialize(); - (void) argc; - (void) argv; + test_filter(); + test_sort(); + test_custom(); + test_enoent(); + test_enotdir(); + test_versionsort(); + test_large(); - /* Initialize random number generator */ - srand((unsigned) time(NULL)); + cleanup(); + return EXIT_SUCCESS; +} - /* Basic scan with simple filter function */ - { - /* Read directory entries */ - n = scandir("tests/3", &files, only_readme, alphasort); - assert(n == 1); +static void +test_filter(void) +{ + /* Read directory entries with filter */ + struct dirent **files; + int n = scandir("tests/3", &files, only_readme, alphasort); + assert(n == 1); - /* Make sure that the filter works */ - assert(strcmp(files[0]->d_name, "README.txt") == 0); + /* Make sure that the filter works */ + assert(strcmp(files[0]->d_name, "README.txt") == 0); - /* Release file names */ - for (i = 0; i < n; i++) { - free(files[i]); - } - free(files); + /* Release file names */ + for (int i = 0; i < n; i++) { + free(files[i]); } + free(files); +} - /* Basic scan with default sorting function */ - { - /* Read directory entries in alphabetic order */ - n = scandir("tests/3", &files, NULL, alphasort); - assert(n == 13); - - /* Make sure that we got all the names in the proper order */ - assert(strcmp(files[0]->d_name, ".") == 0); - assert(strcmp(files[1]->d_name, "..") == 0); - assert(strcmp(files[2]->d_name, "3zero.dat") == 0); - assert(strcmp(files[3]->d_name, "666.dat") == 0); - assert(strcmp(files[4]->d_name, "Qwerty-my-aunt.dat") == 0); - assert(strcmp(files[5]->d_name, "README.txt") == 0); - assert(strcmp(files[6]->d_name, "aaa.dat") == 0); - assert(strcmp(files[7]->d_name, "dirent.dat") == 0); - assert(strcmp(files[8]->d_name, "empty.dat") == 0); - assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0); - assert(strcmp(files[10]->d_name, "sane-1.2.30.dat") == 0); - assert(strcmp(files[11]->d_name, "sane-1.2.4.dat") == 0); - assert(strcmp(files[12]->d_name, "zebra.dat") == 0); - - /* Release file names */ - for (i = 0; i < n; i++) { - free(files[i]); - } - free(files); +static void +test_sort(void) +{ + /* Read directory entries in alphabetic order */ + struct dirent **files; + int n = scandir("tests/3", &files, NULL, alphasort); + assert(n == 13); + + /* Make sure that we got all the names in the proper order */ + assert(strcmp(files[0]->d_name, ".") == 0); + assert(strcmp(files[1]->d_name, "..") == 0); + assert(strcmp(files[2]->d_name, "3zero.dat") == 0); + assert(strcmp(files[3]->d_name, "666.dat") == 0); + assert(strcmp(files[4]->d_name, "Qwerty-my-aunt.dat") == 0); + assert(strcmp(files[5]->d_name, "README.txt") == 0); + assert(strcmp(files[6]->d_name, "aaa.dat") == 0); + assert(strcmp(files[7]->d_name, "dirent.dat") == 0); + assert(strcmp(files[8]->d_name, "empty.dat") == 0); + assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0); + assert(strcmp(files[10]->d_name, "sane-1.2.30.dat") == 0); + assert(strcmp(files[11]->d_name, "sane-1.2.4.dat") == 0); + assert(strcmp(files[12]->d_name, "zebra.dat") == 0); + + /* Release file names */ + for (int i = 0; i < n; i++) { + free(files[i]); } + free(files); +} - /* Custom filter AND sort function */ - { - /* Read directory entries in alphabetic order */ - n = scandir("tests/3", &files, no_directories, reverse_alpha); - assert(n == 11); - - /* Make sure that we got file names in the reverse order */ - assert(strcmp(files[0]->d_name, "zebra.dat") == 0); - assert(strcmp(files[1]->d_name, "sane-1.2.4.dat") == 0); - assert(strcmp(files[2]->d_name, "sane-1.2.30.dat") == 0); - assert(strcmp(files[3]->d_name, "sane-1.12.0.dat") == 0); - assert(strcmp(files[4]->d_name, "empty.dat") == 0); - assert(strcmp(files[5]->d_name, "dirent.dat") == 0); - assert(strcmp(files[6]->d_name, "aaa.dat") == 0); - assert(strcmp(files[7]->d_name, "README.txt") == 0); - assert(strcmp(files[8]->d_name, "Qwerty-my-aunt.dat") == 0); - assert(strcmp(files[9]->d_name, "666.dat") == 0); - assert(strcmp(files[10]->d_name, "3zero.dat") == 0); - - /* Release file names */ - for (i = 0; i < n; i++) { - free(files[i]); - } - free(files); +static void +test_custom(void) +{ + /* Read directory entries in alphabetic order */ + struct dirent **files; + int n = scandir("tests/3", &files, no_directories, reverse_alpha); + assert(n == 11); + + /* Make sure that we got file names in the reverse order */ + assert(strcmp(files[0]->d_name, "zebra.dat") == 0); + assert(strcmp(files[1]->d_name, "sane-1.2.4.dat") == 0); + assert(strcmp(files[2]->d_name, "sane-1.2.30.dat") == 0); + assert(strcmp(files[3]->d_name, "sane-1.12.0.dat") == 0); + assert(strcmp(files[4]->d_name, "empty.dat") == 0); + assert(strcmp(files[5]->d_name, "dirent.dat") == 0); + assert(strcmp(files[6]->d_name, "aaa.dat") == 0); + assert(strcmp(files[7]->d_name, "README.txt") == 0); + assert(strcmp(files[8]->d_name, "Qwerty-my-aunt.dat") == 0); + assert(strcmp(files[9]->d_name, "666.dat") == 0); + assert(strcmp(files[10]->d_name, "3zero.dat") == 0); + + /* Release file names */ + for (int i = 0; i < n; i++) { + free(files[i]); } + free(files); +} - /* Trying to read from non-existent directory leads to an error */ - { - files = NULL; - n = scandir("tests/invalid", &files, NULL, alphasort); - assert(n == -1); - assert(files == NULL); - assert(errno == ENOENT); - } +static void +test_enoent(void) +{ + /* Trying to open non-existing file produces an error */ + struct dirent **files = NULL; + int n = scandir("tests/invalid", &files, NULL, alphasort); + assert(n == -1); + assert(files == NULL); + assert(errno == ENOENT); +} +static void +test_enotdir(void) +{ /* Trying to open file as a directory produces ENOTDIR error */ - { - files = NULL; - n = scandir("tests/3/666.dat", &files, NULL, alphasort); - assert(n == -1); - assert(files == NULL); - assert(errno == ENOTDIR); - } + struct dirent **files = NULL; + int n = scandir("tests/3/666.dat", &files, NULL, alphasort); + assert(n == -1); + assert(files == NULL); + assert(errno == ENOTDIR); +} +static void +test_versionsort(void) +{ /* Sort files using versionsort() */ - { - files = NULL; - n = scandir("tests/3", &files, no_directories, versionsort); - assert(n == 11); - - /* - * Make sure that we got all the file names in the proper order: - * 1.2.4 < 1.2.30 < 1.12.0 - */ - assert(strcmp(files[0]->d_name, "3zero.dat") == 0); - assert(strcmp(files[1]->d_name, "666.dat") == 0); - assert(strcmp(files[2]->d_name, "Qwerty-my-aunt.dat") == 0); - assert(strcmp(files[3]->d_name, "README.txt") == 0); - assert(strcmp(files[4]->d_name, "aaa.dat") == 0); - assert(strcmp(files[5]->d_name, "dirent.dat") == 0); - assert(strcmp(files[6]->d_name, "empty.dat") == 0); - assert(strcmp(files[7]->d_name, "sane-1.2.4.dat") == 0); - assert(strcmp(files[8]->d_name, "sane-1.2.30.dat") == 0); - assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0); - assert(strcmp(files[10]->d_name, "zebra.dat") == 0); - - /* Release file names */ - for (i = 0; i < n; i++) { - free(files[i]); - } - free(files); + struct dirent **files = NULL; + int n = scandir("tests/3", &files, no_directories, versionsort); + assert(n == 11); + + /* + * Make sure that we got all the file names in the proper order: + * 1.2.4 < 1.2.30 < 1.12.0 + */ + assert(strcmp(files[0]->d_name, "3zero.dat") == 0); + assert(strcmp(files[1]->d_name, "666.dat") == 0); + assert(strcmp(files[2]->d_name, "Qwerty-my-aunt.dat") == 0); + assert(strcmp(files[3]->d_name, "README.txt") == 0); + assert(strcmp(files[4]->d_name, "aaa.dat") == 0); + assert(strcmp(files[5]->d_name, "dirent.dat") == 0); + assert(strcmp(files[6]->d_name, "empty.dat") == 0); + assert(strcmp(files[7]->d_name, "sane-1.2.4.dat") == 0); + assert(strcmp(files[8]->d_name, "sane-1.2.30.dat") == 0); + assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0); + assert(strcmp(files[10]->d_name, "zebra.dat") == 0); + + /* Release file names */ + for (int i = 0; i < n; i++) { + free(files[i]); } + free(files); +} - /* Scan large directory */ - { - char dirname[PATH_MAX+1]; - int i; - int ok; +static void +test_large(void) +{ + char dirname[PATH_MAX+1]; + int i; + int ok; - /* Copy name of temporary directory to variable dirname */ + /* Copy name of temporary directory to variable dirname */ #ifdef WIN32 - i = GetTempPathA(PATH_MAX, dirname); - assert(i > 0); + i = GetTempPathA(PATH_MAX, dirname); + assert(i > 0); #else - strcpy(dirname, "/tmp/"); - i = strlen(dirname); + strcpy(dirname, "/tmp/"); + i = strlen(dirname); #endif - /* Append random characters to dirname */ - for (int j = 0; j < 10; j++) { - char c; - - /* Generate random character */ - c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; + /* Append random characters to dirname */ + for (size_t j = 0; j < 10; j++) { + char c; - /* Append character to dirname */ - assert(i < PATH_MAX); - dirname[i++] = c; - } + /* Generate random character */ + c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; - /* Terminate directory name */ + /* Append character to dirname */ assert(i < PATH_MAX); - dirname[i] = '\0'; + dirname[i++] = c; + } - /* Create directory */ + /* Terminate directory name */ + assert(i < PATH_MAX); + dirname[i] = '\0'; + + /* Create directory */ #ifdef WIN32 - ok = CreateDirectoryA(dirname, NULL); - assert(ok); + ok = CreateDirectoryA(dirname, NULL); + assert(ok); #else - ok = mkdir(dirname, 0700); - assert(ok == /*success*/0); + ok = mkdir(dirname, 0700); + assert(ok == /*success*/0); #endif - /* Create one thousand files */ - assert(i + 5 < PATH_MAX); - for (int j = 0; j < 1000; j++) { - FILE *fp; - - /* Construct file name */ - dirname[i] = '/'; - dirname[i+1] = 'z'; - dirname[i+2] = '0' + ((j / 100) % 10); - dirname[i+3] = '0' + ((j / 10) % 10); - dirname[i+4] = '0' + (j % 10); - dirname[i+5] = '\0'; - - /* Create file */ - fp = fopen(dirname, "w"); - assert(fp != NULL); - fclose(fp); - - } - - /* Cut out the file name part */ - dirname[i] = '\0'; - - /* Scan directory */ - n = scandir(dirname, &files, no_directories, alphasort); - assert(n == 1000); - - /* Make sure that all 1000 files are read back */ - for (int j = 0; j < n; j++) { - char match[100]; - - /* Construct file name */ - match[0] = 'z'; - match[1] = '0' + ((j / 100) % 10); - match[2] = '0' + ((j / 10) % 10); - match[3] = '0' + (j % 10); - match[4] = '\0'; - - /* Make sure that file name matches that on the disk */ - assert(strcmp(files[j]->d_name, match) == 0); - - } - - /* Release file names */ - for (int j = 0; j < n; j++) { - free(files[j]); - } - free(files); + /* Create one thousand files */ + assert(i + 5 < PATH_MAX); + for (int j = 0; j < 1000; j++) { + FILE *fp; + + /* Construct file name */ + dirname[i] = '/'; + dirname[i+1] = 'z'; + dirname[i+2] = '0' + ((j / 100) % 10); + dirname[i+3] = '0' + ((j / 10) % 10); + dirname[i+4] = '0' + (j % 10); + dirname[i+5] = '\0'; + + /* Create file */ + fp = fopen(dirname, "w"); + assert(fp != NULL); + fclose(fp); + } - printf("OK\n"); - return EXIT_SUCCESS; + /* Cut out the file name part */ + dirname[i] = '\0'; + + /* Scan directory */ + struct dirent **files; + int n = scandir(dirname, &files, no_directories, alphasort); + assert(n == 1000); + + /* Make sure that all 1000 files are read back */ + for (int j = 0; j < n; j++) { + char match[100]; + + /* Construct file name */ + match[0] = 'z'; + match[1] = '0' + ((j / 100) % 10); + match[2] = '0' + ((j / 10) % 10); + match[3] = '0' + (j % 10); + match[4] = '\0'; + + /* Make sure that file name matches that on the disk */ + assert(strcmp(files[j]->d_name, match) == 0); + } + + /* Release file names */ + for (int j = 0; j < n; j++) { + free(files[j]); + } + free(files); } /* Only pass README.txt file */ @@ -261,7 +282,7 @@ only_readme(const struct dirent *entry) return strcmp(entry->d_name, "README.txt") == 0; } -/* Filter out directories */ +/* Only pass regular files */ static int no_directories(const struct dirent *entry) { @@ -274,3 +295,16 @@ reverse_alpha(const struct dirent **a, const struct dirent **b) { return strcoll((*b)->d_name, (*a)->d_name); } + +static void +initialize(void) +{ + /* Initialize random number generator */ + srand((unsigned) time(NULL)); +} + +static void +cleanup(void) +{ + printf("OK\n"); +} diff --git a/tests/t-strverscmp.c b/tests/t-strverscmp.c index fcb044f..c563316 100644 --- a/tests/t-strverscmp.c +++ b/tests/t-strverscmp.c @@ -13,17 +13,32 @@ #include #include #include -#include #include #include +#undef NDEBUG +#include + +static void test_compare(void); +static void test_performance(void); +static void initialize(void); +static void cleanup(void); + int -main( - int argc, char *argv[]) +main(void) { - (void) argc; - (void) argv; + initialize(); + + test_compare(); + test_performance(); + cleanup(); + return EXIT_SUCCESS; +} + +static void +test_compare(void) +{ /* Strings without digits are compared as in strcmp() */ assert(strverscmp("", "") == 0); assert(strverscmp("abc", "abc") == 0); @@ -165,42 +180,52 @@ main( assert(strverscmp("1", "10") < 0); assert(strverscmp("9", "10") < 0); +} - /* Compare speed */ - { +static void +test_performance(void) +{ #define LENGTH 100 #define REPEAT 1000000 - char a[LENGTH+1]; - char b[LENGTH+1]; - size_t i; - size_t j; - char letters[] = "01234567890123456789abdefghjkpqrtwxyz-/."; - size_t n = strlen(letters); - - /* Repeat test */ - for(i = 0; i < REPEAT; i++) { - int diff1; - int diff2; - - /* Generate two random strings of LENGTH characters */ - for(j = 0; j < LENGTH; j++) { - a[j] = letters[rand() % n]; - b[j] = letters[rand() % n]; - } - a[j] = '\0'; - b[j] = '\0'; - - /* Compare strings in both directions */ - diff1 = strverscmp(a, b); - diff2 = strverscmp(b, a); - - /* Must give identical result in both directions */ - assert((diff1 < 0 && diff2 > 0) - || (diff1 == 0 && diff2 == 0) - || (diff1 > 0 && diff2 < 0)); + char a[LENGTH+1]; + char b[LENGTH+1]; + size_t i; + size_t j; + char letters[] = "01234567890123456789abdefghjkpqrtwxyz-/."; + size_t n = strlen(letters); + + /* Repeat test */ + for(i = 0; i < REPEAT; i++) { + int diff1; + int diff2; + + /* Generate two random strings of LENGTH characters */ + for(j = 0; j < LENGTH; j++) { + a[j] = letters[rand() % n]; + b[j] = letters[rand() % n]; } + a[j] = '\0'; + b[j] = '\0'; + + /* Compare strings in both directions */ + diff1 = strverscmp(a, b); + diff2 = strverscmp(b, a); + + /* Must give identical result in both directions */ + assert((diff1 < 0 && diff2 > 0) + || (diff1 == 0 && diff2 == 0) + || (diff1 > 0 && diff2 < 0)); } +} +static void +initialize(void) +{ + /*NOP*/; +} + +static void +cleanup(void) +{ printf("OK\n"); - return EXIT_SUCCESS; } diff --git a/tests/t-symlink.c b/tests/t-symlink.c index 53eb993..5bafa90 100644 --- a/tests/t-symlink.c +++ b/tests/t-symlink.c @@ -7,47 +7,285 @@ * https://github.com/tronkko/dirent */ +/* Silence warning about strcmp being insecure (MS Visual Studio) */ +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include #include +#include +#include +#ifdef _MSC_VER +# include +# include +# define mkdir(path, mode) _mkdir(path) +# define stat _stat64 +# define fstat _fstat64 +# define open _open +# define close _close +#else +# include +#endif + +#ifdef WIN32 +# define SEP "\\" +#else +# define SEP "/" +#endif + +#undef NDEBUG #include -#include -int countSymlink(const char* dirName) { - DIR* dir = opendir(dirName); - if (!dir) { - fprintf(stderr, "Open directory error: %d", GetLastError()); - return 1; - } - - struct dirent *ent; - int found = 0; - /* Read the directory and count symlinks */ - while ((ent = readdir(dir)) != NULL) - found += (ent->d_type == DT_LNK); - return found; -} - -int main(int argc, char** argv) { - /* -s makes link creation failures fatal. Symlink creation requires - * root access, unless a so-called "Developer Mode" is on, therefore - * by default creation failure is not a fatal error. */ - int failRet = (argc >= 2 && strcmp(argv[1], "-s") == 0) ? 127 : 0; - DWORD flag = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - - /* Chances are that the links are created in previous runs. */ - if (countSymlink("tests\\1") >= 2) - return 0; - - /* Create symlinks */ - if (!CreateSymbolicLinkA("tests\\1\\link_file", ".\\file", flag)) { - fprintf(stderr, "Create file symlink error: %d", GetLastError()); - return failRet; - } - flag |= SYMBOLIC_LINK_FLAG_DIRECTORY; - if (!CreateSymbolicLinkA("tests\\1\\link_dir", ".\\dir", flag)) { - fprintf(stderr, "Create file symlink error: %d", GetLastError()); - return failRet; - } - - assert(countSymlink("tests\\1") >= 2); - return 0; +static void test_stat(void); +static void test_dirent(void); +static int is_link(const char *fn); +static int is_file(const char *fn); +static int is_directory(const char *fn); +static void initialize(void); +static void cleanup(void); + +static char tmpdir[PATH_MAX+1]; +static char subdir[PATH_MAX + 1]; +static char file[PATH_MAX + 1]; +static char link1[PATH_MAX + 1]; +static char link2[PATH_MAX + 1]; + +int +main(void) +{ + initialize(); + + test_stat(); + test_dirent(); + + cleanup(); + return EXIT_SUCCESS; +} + +static void +test_stat(void) +{ + assert(is_file(file)); + assert(!is_directory(file)); + assert(!is_link(file)); + + assert(!is_file(subdir)); + assert(is_directory(subdir)); + assert(!is_link(subdir)); + + assert(is_file(link1)); + assert(!is_directory(link1)); + assert(is_link(link1)); + + assert(!is_file(link2)); + assert(is_directory(link2)); + assert(is_link(link2)); +} + +static void +test_dirent(void) +{ +#ifdef _DIRENT_HAVE_D_TYPE + /* Open directory */ + DIR *dir = opendir(tmpdir); + if (dir == NULL) { + fprintf(stderr, "Directory %s not found\n", tmpdir); + abort(); + } + + /* Read entries in directory */ + struct dirent *ent; + int found = 0; + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + assert(ent->d_type == DT_DIR); + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + assert(ent->d_type == DT_DIR); + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + assert(ent->d_type == DT_REG); + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + assert(ent->d_type == DT_DIR); + found += 8; + } else if (strcmp(ent->d_name, "link1") == 0) { + assert(ent->d_type == DT_LNK); + found += 16; + } else if (strcmp(ent->d_name, "link2") == 0) { + assert(ent->d_type == DT_LNK); + found += 32; + } else { + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 1 + 2 + 4 + 8 + 16 + 32); + + closedir(dir); +#else + fprintf(stderr, "Skippped test_dirent\n"); +#endif +} + +/* Returns true if file name is a symbolic link */ +static int +is_link(const char *fn) +{ +#ifdef WIN32 + /* In Windows, function lstat does not exist */ + DWORD attr = GetFileAttributesA(fn); + if (attr == INVALID_FILE_ATTRIBUTES) { + return 0; + } + return (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0; +#else + /* In Linux, function lstat returns information about the link */ + struct stat buf; + if (lstat(fn, &buf) != /*OK*/0) { + return 0; + } + return S_ISLNK(buf.st_mode); +#endif +} + +/* Returns true if file name refers to a regular file */ +static int +is_file(const char *fn) +{ + struct stat buf; + + if (stat(fn, &buf) != /*OK*/0) { + return 0; + } + + return S_ISREG(buf.st_mode); +} + +/* Returns true if file name refers to a directory */ +static int +is_directory(const char *fn) +{ + struct stat buf; + + if (stat(fn, &buf) != /*OK*/0) { + return 0; + } + + return S_ISDIR(buf.st_mode); +} + +static void +initialize(void) +{ + size_t i; + + /* + * Select UTF-8 locale. This will change the way how C runtime + * functions such as fopen() and mkdir() handle character strings. + * For more information, please see: + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-160#utf-8-support + */ + setlocale(LC_ALL, "LC_CTYPE=.utf8"); + + /* Initialize random number generator from time */ + srand(((int) time(NULL)) * 257 + 5555); + + /* Get system temporary directory */ +#ifdef WIN32 + i = GetTempPathA(PATH_MAX, tmpdir); + assert(i > 0); + assert(tmpdir[i - 1] == '\\'); +#else + strcpy(tmpdir, "/tmp/"); + i = strlen(tmpdir); +#endif + + /* Append random directory name to path name */ + for (size_t k = 0; k < 10; k++) { + /* Generate random character */ + char c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; + + /* Append character to path */ + assert(i < PATH_MAX); + tmpdir[i++] = c; + } + + /* Zero-terminate the directory name */ + assert(i < PATH_MAX); + tmpdir[i] = '\0'; + + /* Create directory for test files */ + if (mkdir(tmpdir, S_IRWXU) != /*OK*/0) { + fprintf(stderr, "Cannot create directory %s\n", tmpdir); + abort(); + } + printf("Created directory %s\n", tmpdir); + + /* Create subdirectory */ + strcpy(subdir, tmpdir); + strcat(subdir, SEP "dir"); + if (mkdir(subdir, S_IRWXU) != /*OK*/0) { + fprintf(stderr, "Cannot create directory %s\n", subdir); + abort(); + } + + /* Create regular file */ + strcpy(file, tmpdir); + strcat(file, SEP "file"); + FILE *fp = fopen(file, "w"); + if (!fp) { + fprintf(stderr, "Cannot create file %s\n", file); + abort(); + } + fprintf(fp, "Hello world!\n"); + fclose(fp); + + /* Create symbolic link */ + strcpy(link1, tmpdir); + strcat(link1, "/link1"); +#ifdef WIN32 + DWORD flags1 = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (!CreateSymbolicLinkA(link1, file, flags1)) { + /* Developer mode not activated? */ + fprintf(stderr, "Create file symlink error: %d\n", GetLastError()); + fprintf(stderr, "Skipped\n"); + exit(/*Skip*/ 77); + } +#else + if (symlink(file, link1) != /*OK*/0) { + fprintf(stderr, "Cannot create link %s->%s\n", file, link1); + abort(); + } +#endif + + /* Create symbolic link to directory */ + strcpy(link2, tmpdir); + strcat(link2, "/link2"); +#ifdef WIN32 + DWORD flags2 = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE + | SYMBOLIC_LINK_FLAG_DIRECTORY; + if (!CreateSymbolicLinkA(link2, subdir, flags2)) { + fprintf(stderr, "Create file symlink error: %d\n", GetLastError()); + fprintf(stderr, "Skipped\n"); + exit(/*Skip*/ 77); + } +#else + if (symlink(subdir, link2) != /*OK*/0) { + fprintf(stderr, "Cannot create link %s->%s\n", subdir, link2); + abort(); + } +#endif +} + +static void +cleanup(void) +{ + printf("OK\n"); } diff --git a/tests/t-telldir.c b/tests/t-telldir.c index e2a1763..f8a5138 100644 --- a/tests/t-telldir.c +++ b/tests/t-telldir.c @@ -14,21 +14,22 @@ #include #include #include -#include #undef NDEBUG #include static void test_telldir(void); +static void initialize(void); +static void cleanup(void); int -main(int argc, char *argv[]) +main(void) { - (void) argc; - (void) argv; + initialize(); test_telldir(); + cleanup(); return EXIT_SUCCESS; } @@ -163,3 +164,14 @@ test_telldir(void) assert(ent == NULL); } +static void +initialize(void) +{ + /*NOP*/; +} + +static void +cleanup(void) +{ + printf("OK\n"); +} diff --git a/tests/t-unicode.c b/tests/t-unicode.c index 4a88322..3ab1ff5 100644 --- a/tests/t-unicode.c +++ b/tests/t-unicode.c @@ -21,121 +21,46 @@ #undef NDEBUG #include -int -main(int argc, char *argv[]) -{ -#ifdef WIN32 - wchar_t wpath[MAX_PATH+1]; - char path[MAX_PATH+1]; - DWORD i, j, k, x; - BOOL ok; - HANDLE fh; - _WDIR *wdir; - struct _wdirent *wentry; - DIR *dir; - struct dirent *entry; - char buffer[100]; - FILE *fp; - int counter = 0; - - /* Initialize random number generator */ - srand(((int) time(NULL)) * 257 + ((int) GetCurrentProcessId())); - - /* Set current locale */ - if (argc > 1) { - printf("Locale %s\n", argv[1]); - setlocale(LC_ALL, argv[1]); - } else { - setlocale(LC_ALL, ""); - } - - /****** CREATE FILE WITH UNICODE FILE NAME ******/ - - /* Get path to temporary directory (wide-character and ascii) */ - i = GetTempPathW(MAX_PATH, wpath); - assert(i > 0); - j = GetTempPathA(MAX_PATH, path); - assert(j > 0); - - /* Append random directory name */ - for (k = 0; k < 10; k++) { - /* Generate random character */ - char c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; - - /* Append character to paths */ - assert(i < MAX_PATH && j < MAX_PATH); - wpath[i++] = c; - path[j++] = c; - } +static void test_wcs(void); +static void test_mbs(void); +static void test_utf8(void); +static void initialize(void); +static void cleanup(void); - /* Terminate paths */ - assert(i < MAX_PATH && j < MAX_PATH); - wpath[i] = '\0'; - path[j] = '\0'; - - /* Remember the end of directory name */ - k = i; - - /* Create directory using unicode */ - ok = CreateDirectoryW(wpath, NULL); - if (!ok) { - DWORD e = GetLastError(); - wprintf(L"Cannot create directory %ls (code %u)\n", wpath, e); - abort(); - } - - /* Overwrite zero terminator with path separator */ - assert(i < MAX_PATH && j < MAX_PATH); - wpath[i++] = '\\'; - - /* Append a few unicode characters */ - assert(i < MAX_PATH); - wpath[i++] = 0x6d4b; - assert(i < MAX_PATH); - wpath[i++] = 0x8bd5; - - /* Terminate string */ - assert(i < MAX_PATH); - wpath[i] = '\0'; - - /* Create file with unicode */ - fh = CreateFileW( - wpath, - /* Access */ GENERIC_READ | GENERIC_WRITE, - /* Share mode */ 0, - /* Security attributes */ NULL, - /* Creation disposition */ CREATE_NEW, - /* Attributes */ FILE_ATTRIBUTE_NORMAL, - /* Template files */ NULL - ); - assert(fh != INVALID_HANDLE_VALUE); +#ifdef WIN32 +static wchar_t wpath[MAX_PATH + 1]; +static char path[MAX_PATH + 1]; +#endif - /* Write some data to file */ - ok = WriteFile( - /* File handle */ fh, - /* Pointer to data */ "hep\n", - /* Number of bytes to write */ 4, - /* Number of bytes written */ NULL, - /* Overlapped */ NULL - ); - assert(ok); +int +main(void) +{ + initialize(); - /* Close file */ - ok = CloseHandle(fh); - assert(ok); + test_wcs(); + test_mbs(); - /****** MAKE SURE THAT UNICODE FILE CAN BE READ BY _WREADDIR ******/ + cleanup(); + return EXIT_SUCCESS; +} - /* Zero terminate wide-character path and open directory stream */ - wpath[k] = '\0'; - wdir = _wopendir(wpath); +static void +test_wcs(void) +{ +#ifdef WIN32 + /* Open directory stream using wide-character string */ + _WDIR *wdir = _wopendir(wpath); if (wdir == NULL) { wprintf(L"Cannot open directory %ls\n", wpath); abort(); } + /* Compute length of directory name */ + DWORD k = wcslen(wpath); + /* Read through entries */ - counter = 0; + int counter = 0; + struct _wdirent *wentry; while ((wentry = _wreaddir(wdir)) != NULL) { /* Skip pseudo directories */ if (wcscmp(wentry->d_name, L".") == 0) { @@ -149,11 +74,11 @@ main(int argc, char *argv[]) counter++; assert(wentry->d_type == DT_REG); - /* Append file name to path */ - i = k; + /* Modify wpath by appending file name to it */ + DWORD i = k; assert(i < MAX_PATH); wpath[i++] = '\\'; - x = 0; + DWORD x = 0; while (wentry->d_name[x] != '\0') { assert(i < MAX_PATH); wpath[i++] = wentry->d_name[x++]; @@ -162,7 +87,7 @@ main(int argc, char *argv[]) wpath[i] = '\0'; /* Open file for read */ - fh = CreateFileW( + HANDLE fh = CreateFileW( wpath, /* Access */ GENERIC_READ, /* Share mode */ 0, @@ -174,7 +99,8 @@ main(int argc, char *argv[]) assert(fh != INVALID_HANDLE_VALUE); /* Read data from file */ - ok = ReadFile( + char buffer[100]; + BOOL ok = ReadFile( /* File handle */ fh, /* Output buffer */ buffer, /* Max number of bytes to read */ sizeof(buffer) - 1, @@ -194,24 +120,35 @@ main(int argc, char *argv[]) ok = CloseHandle(fh); assert(ok); } + + /* The directory only has one file */ assert(counter == 1); /* Close directory */ _wclosedir(wdir); - /****** MAKE SURE THAT UNICODE FILE NAME CAN BE READ BY READDIR *****/ + /* Restore the original path name */ + wpath[k] = '\0'; +#endif +} - /* Zero terminate ascii path and open directory stream */ - k = j; - path[k] = '\0'; - dir = opendir(path); +static void +test_mbs(void) +{ +#ifdef WIN32 + /* Open directory stream using multi-byte character string */ + DIR *dir = opendir(path); if (dir == NULL) { fprintf(stderr, "Cannot open directory %s\n", path); abort(); } + /* Compute length of directory name */ + DWORD k = strlen(path); + /* Read through entries */ - counter = 0; + int counter = 0; + struct dirent *entry; while ((entry = readdir(dir)) != NULL) { /* Skip pseudo directories */ if (strcmp(entry->d_name, ".") == 0) { @@ -225,11 +162,11 @@ main(int argc, char *argv[]) counter++; assert(entry->d_type == DT_REG); - /* Append file name to path */ - j = k; + /* Modify path by appending file name to it */ + DWORD j = k; assert(j < MAX_PATH); path[j++] = '\\'; - x = 0; + DWORD x = 0; while (entry->d_name[x] != '\0') { assert(j < MAX_PATH); path[j++] = entry->d_name[x++]; @@ -238,13 +175,14 @@ main(int argc, char *argv[]) path[j] = '\0'; /* Open file for read */ - fp = fopen(path, "r"); + FILE *fp = fopen(path, "r"); if (!fp) { fprintf(stderr, "Cannot open file %s\n", path); abort(); } /* Read data from file */ + char buffer[100]; if (fgets(buffer, sizeof(buffer), fp) == NULL) { fprintf(stderr, "Cannot read file %s\n", path); abort(); @@ -260,15 +198,27 @@ main(int argc, char *argv[]) /* Close file */ fclose(fp); } + + /* The directory has only one file */ assert(counter == 1); /* Close directory */ closedir(dir); - /****** CREATE FILE WITH UTF-8 ******/ + /* Restore the original path */ + path[k] = '\0'; +#endif +} + +static void +test_utf8(void) +{ +#ifdef WIN32 + /* Compute length of directory name */ + DWORD k = strlen(path); /* Append UTF-8 file name (åäö.txt) to path */ - j = k; + DWORD j = k; path[j++] = '\\'; path[j++] = 0xc3; path[j++] = 0xa5; @@ -284,16 +234,16 @@ main(int argc, char *argv[]) path[j] = '\0'; /* - * Create file. + * Create file using UTF-8 file name. * - * Be ware that the code below creates a different file depending on - * the current locale! For example, if the current locale is + * Be ware that the code below creates a different file name depending + * on the current locale! For example, if the current locale is * english_us.65001, then the file name will be "åäö.txt" (7 * characters). However, if the current locale is english_us.1252, * then the file name will be "ÃċÃĊö.txt" (10 characters). */ printf("Creating %s\n", path); - fp = fopen(path, "w"); + FILE *fp = fopen(path, "w"); if (!fp) { fprintf(stderr, "Cannot open file %s\n", path); abort(); @@ -301,16 +251,19 @@ main(int argc, char *argv[]) fputs("hep\n", fp); fclose(fp); - /* Open directory again */ + /* Restore original path */ path[k] = '\0'; - dir = opendir(path); + + /* Open directory stream */ + DIR *dir = opendir(path); if (dir == NULL) { fprintf(stderr, "Cannot open directory %s\n", path); abort(); } /* Read through entries */ - counter = 0; + int counter = 0; + struct dirent *entry; while ((entry = readdir(dir)) != NULL) { /* Skip pseudo directories */ if (strcmp(entry->d_name, ".") == 0) { @@ -324,11 +277,11 @@ main(int argc, char *argv[]) counter++; assert(entry->d_type == DT_REG); - /* Append file name to path */ + /* Modify path by appending file name to it */ j = k; assert(j < MAX_PATH); path[j++] = '\\'; - x = 0; + DWORD x = 0; while (entry->d_name[x] != '\0') { assert(j < MAX_PATH); path[j++] = entry->d_name[x++]; @@ -345,7 +298,7 @@ main(int argc, char *argv[]) } printf("\n"); - /* Open file for read */ + /* Open file for read with UTF-8 file name */ fp = fopen(path, "r"); if (!fp) { fprintf(stderr, "Cannot open file %s\n", path); @@ -353,6 +306,7 @@ main(int argc, char *argv[]) } /* Read data from file */ + char buffer[100]; if (fgets(buffer, sizeof(buffer), fp) == NULL) { fprintf(stderr, "Cannot read file %s\n", path); abort(); @@ -368,14 +322,115 @@ main(int argc, char *argv[]) /* Close file */ fclose(fp); } + + /* We now had two files with identical contents */ assert(counter == 2); /* Close directory */ closedir(dir); +#endif +} + +static void +initialize(void) +{ +#ifdef WIN32 + /* + * Select UTF-8 locale. This will change the way how C runtime + * functions such as fopen() and mkdir() handle character strings. + * For more information, please see: + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-160#utf-8-support + */ + setlocale(LC_ALL, "LC_CTYPE=.utf8"); + + /* Initialize random number generator */ + srand(((int) time(NULL)) * 257 + ((int) GetCurrentProcessId())); + + /* Get path to temporary directory (wide-character and ascii) */ + DWORD i = GetTempPathW(MAX_PATH, wpath); + assert(i > 0); + DWORD j = GetTempPathA(MAX_PATH, path); + assert(j > 0); + + /* Append random directory name to both paths */ + DWORD k; + for (k = 0; k < 10; k++) { + /* Generate random character */ + char c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; + + /* Append character to paths */ + assert(i < MAX_PATH && j < MAX_PATH); + wpath[i++] = c; + path[j++] = c; + } + + /* Terminate paths */ + assert(i < MAX_PATH && j < MAX_PATH); + wpath[i] = '\0'; + path[j] = '\0'; + + /* Remember the end of directory name */ + k = i; + + /* Create directory using unicode */ + BOOL ok = CreateDirectoryW(wpath, NULL); + if (!ok) { + DWORD e = GetLastError(); + wprintf(L"Cannot create directory %ls (code %u)\n", wpath, e); + abort(); + } + + /* Overwrite zero terminator with path separator */ + assert(i < MAX_PATH && j < MAX_PATH); + wpath[i++] = '\\'; + + /* Append a few unicode characters */ + assert(i < MAX_PATH); + wpath[i++] = 0x6d4b; + assert(i < MAX_PATH); + wpath[i++] = 0x8bd5; + + /* Terminate string */ + assert(i < MAX_PATH); + wpath[i] = '\0'; + + /* Create file with unicode name */ + HANDLE fh = CreateFileW( + wpath, + /* Access */ GENERIC_READ | GENERIC_WRITE, + /* Share mode */ 0, + /* Security attributes */ NULL, + /* Creation disposition */ CREATE_NEW, + /* Attributes */ FILE_ATTRIBUTE_NORMAL, + /* Template files */ NULL + ); + assert(fh != INVALID_HANDLE_VALUE); + + /* Write some data to file */ + ok = WriteFile( + /* File handle */ fh, + /* Pointer to data */ "hep\n", + /* Number of bytes to write */ 4, + /* Number of bytes written */ NULL, + /* Overlapped */ NULL + ); + assert(ok); + + /* Close file */ + ok = CloseHandle(fh); + assert(ok); + + /* Zero terminate wide-character path */ + wpath[k] = '\0'; #else - /* Linux */ - (void) argc; - (void) argv; + /* This test is not available under UNIX/Linux */ + fprintf(stderr, "Skipped\n"); + exit(/*Skip*/ 77); #endif - return EXIT_SUCCESS; +} + +static void +cleanup(void) +{ + printf("OK\n"); } diff --git a/tests/t-utf8.c b/tests/t-utf8.c index 3bda36c..f26d5a5 100644 --- a/tests/t-utf8.c +++ b/tests/t-utf8.c @@ -21,30 +21,32 @@ #undef NDEBUG #include +static void test_open(void); +static void initialize(void); +static void cleanup(void); + int -main(int argc, char *argv[]) +main(void) { -#ifdef WIN32 - /* - * Select UTF-8 locale. This will change the way how C runtime - * functions such as fopen() and mkdir() handle character strings. - * For more information, please see: - * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-160#utf-8-support - */ - setlocale(LC_ALL, "LC_CTYPE=.utf8"); + initialize(); - /* Initialize random number generator */ - srand(((int) time(NULL)) * 257 + ((int) GetCurrentProcessId())); + test_open(); + + cleanup(); + return EXIT_SUCCESS; +} +static void +test_open(void) +{ +#ifdef WIN32 /* Get path to temporary directory */ wchar_t wpath[MAX_PATH+1]; DWORD i = GetTempPathW(MAX_PATH, wpath); assert(i > 0); - - /* Ensure that path name ends in directory separator */ assert(wpath[i - 1] == '\\'); - /* Append random prefix */ + /* Append random directory name */ DWORD k; for (k = 0; k < 8; k++) { /* Generate random character */ @@ -229,10 +231,32 @@ main(int argc, char *argv[]) /* Close directory */ closedir(dir); +#endif +} + +static void +initialize(void) +{ +#ifdef WIN32 + /* + * Select UTF-8 locale. This will change the way how C runtime + * functions such as fopen() and mkdir() handle character strings. + * For more information, please see: + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-160#utf-8-support + */ + setlocale(LC_ALL, "LC_CTYPE=.utf8"); + + /* Initialize random number generator */ + srand(((int) time(NULL)) * 257 + ((int) GetCurrentProcessId())); #else - /* Linux */ - (void) argc; - (void) argv; + /* This test is not available in UNIX/Linux */ + fprintf(stderr, "Skipped\n"); + exit(/*Skip*/ 77); #endif - return EXIT_SUCCESS; +} + +static void +cleanup(void) +{ + printf("OK\n"); }