#include <stic.h> #include <unistd.h> /* chdir() unlink() */ #include <stdarg.h> /* va_list va_arg() va_copy() va_end() va_start() */ #include <string.h> /* strcpy() */ #include <test-utils.h> #include "../../src/cfg/config.h" #include "../../src/compat/os.h" #include "../../src/ui/ui.h" #include "../../src/utils/dynarray.h" #include "../../src/utils/str.h" #include "../../src/filelist.h" #include "../../src/sort.h" #include "../../src/status.h" #define SIGN(n) ({__typeof(n) _n = (n); (_n < 0) ? -1 : (_n > 0);}) #define ASSERT_STRCMP_EQUAL(a, b) \ do { assert_int_equal(SIGN(a), SIGN(b)); } while(0) static void set_file_list(view_t *view, FileType def_ftype, ...); static int on_case_sensitive_fs(void); SETUP_ONCE() { try_enable_utf8_locale(); } SETUP() { cfg.sort_numbers = 1; view_setup(&lwin); set_file_list(&lwin, FT_REG, "a", "_", "A", NULL); view_setup(&rwin); set_file_list(&rwin, FT_REG, "аааааааааа", "АААААААААА", NULL); update_string(&cfg.shell, ""); } TEARDOWN() { update_string(&cfg.shell, NULL); view_teardown(&lwin); view_teardown(&rwin); } TEST(special_chars_ignore_case_sort) { view_set_sort(lwin.sort, SK_BY_INAME, SK_NONE); sort_view(&lwin); assert_string_equal("_", lwin.dir_entry[0].name); } TEST(symlink_to_dir, IF(not_windows)) { assert_success(chdir(SANDBOX_PATH)); assert_success(make_symlink(".", "self")); view_set_sort(lwin.sort, SK_BY_INAME, SK_NONE); strcpy(lwin.curr_dir, SANDBOX_PATH); replace_string(&lwin.dir_entry[2].name, "self"); lwin.dir_entry[2].type = FT_LINK; lwin.dir_entry[2].origin = lwin.curr_dir; lwin.dir_entry[2].dir_link = 1; cfg.slow_fs_list = strdup(""); sort_view(&lwin); free(cfg.slow_fs_list); cfg.slow_fs_list = NULL; assert_string_equal("self", lwin.dir_entry[0].name); assert_int_equal(0, unlink("self")); assert_int_equal(0, chdir("../..")); } TEST(versort_without_numbers) { const char *s, *t; s = "abc"; t = "abcdef"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdef"; t = "abc"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = ""; t = "abc"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abc"; t = ""; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = ""; t = ""; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdef"; t = "abcdef"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdef"; t = "abcdeg"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdeg"; t = "abcdef"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); } TEST(versort_with_numbers) { const char *s, *t; s = "abcdef0"; t = "abcdef0"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdef0"; t = "abcdef1"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdef1"; t = "abcdef0"; ASSERT_STRCMP_EQUAL(strcmp(s, t), strnumcmp(s, t)); s = "abcdef9"; t = "abcdef10"; assert_true(strnumcmp(s, t) < 0); s = "abcdef10"; t = "abcdef10"; assert_true(strnumcmp(s, t) == 0); s = "abcdef10"; t = "abcdef9"; assert_true(strnumcmp(s, t) > 0); s = "abcdef1.20.0"; t = "abcdef1.5.1"; assert_true(strnumcmp(s, t) > 0); s = "x001"; t = "x1"; assert_true(strnumcmp(s, t) < 0); s = "x1"; t = "x001"; assert_true(strnumcmp(s, t) > 0); } TEST(versort_numbers_only) { const char *s, *t; s = "00"; t = "01"; assert_true(strnumcmp(s, t) < 0); s = "01"; t = "10"; assert_true(strnumcmp(s, t) < 0); s = "10"; t = "11"; assert_true(strnumcmp(s, t) < 0); s = "01"; t = "00"; assert_true(strnumcmp(s, t) > 0); s = "10"; t = "01"; assert_true(strnumcmp(s, t) > 0); s = "11"; t = "10"; assert_true(strnumcmp(s, t) > 0); s = "11"; t = "10"; assert_true(strnumcmp(s, t) > 0); s = "13"; t = "100"; assert_true(strnumcmp(s, t) < 0); s = "100"; t = "13"; assert_true(strnumcmp(s, t) > 0); s = "09"; t = "3"; assert_true(strnumcmp(s, t) > 0); s = "3"; t = "10"; assert_true(strnumcmp(s, t) < 0); } TEST(versort_numbers_only_and_letters_only) { const char *s, *t; s = "A"; t = "10"; assert_true(strnumcmp(s, t) > 0); s = "10"; t = "A"; assert_true(strnumcmp(s, t) < 0); } TEST(versort_zero_and_zerox) { const char *s, *t; s = "0"; t = "01"; assert_true(strnumcmp(s, t) < 0); } TEST(versort_zerox_and_one) { const char *s, *t; s = "00_"; t = "01"; assert_true(strnumcmp(s, t) < 0); } TEST(versort_man_page_example) { /* According to the `man strverscmp`: * 000 < 00 < 01 < 010 < 09 < 0 < 1 < 9 < 10. * In Vifm these conditions differ: * - 000 == 00; * - 010 == 09; * - 09 == 0. */ assert_true(strnumcmp("000", "00") == 0); assert_true(strnumcmp("00", "01") < 0); assert_true(strnumcmp("01", "010") < 0); assert_true(strnumcmp("010", "09") > 0); assert_true(strnumcmp("09", "0") > 0); assert_true(strnumcmp("0", "1") < 0); assert_true(strnumcmp("1", "9") < 0); assert_true(strnumcmp("9", "10") < 0); } TEST(ignore_case_name_sort_breaks_ties_deterministically) { /* If normalized names are equal, byte-by-byte comparison should be used to * break ties. */ view_set_sort(rwin.sort, SK_BY_INAME, SK_NONE); sort_view(&rwin); assert_string_equal("АААААААААА", rwin.dir_entry[0].name); assert_string_equal("аааааааааа", rwin.dir_entry[1].name); } TEST(extensions_of_dot_files_are_sorted_correctly) { view_teardown(&lwin); view_setup(&lwin); set_file_list(&lwin, FT_REG, "disown.c", ".cdargsresult", ".tmux.conf", NULL); view_set_sort(lwin.sort, SK_BY_EXTENSION, SK_NONE); sort_view(&lwin); assert_string_equal(".cdargsresult", lwin.dir_entry[0].name); assert_string_equal("disown.c", lwin.dir_entry[1].name); assert_string_equal(".tmux.conf", lwin.dir_entry[2].name); } TEST(sorting_has_no_integer_overflows) { view_teardown(&lwin); view_setup(&lwin); set_file_list(&lwin, FT_REG, "\xff", "\x81", "\x01", "\x80", "\x7f", NULL); view_set_sort(lwin.sort, SK_BY_NAME, SK_NONE); sort_view(&lwin); assert_string_equal("\x01", lwin.dir_entry[0].name); assert_string_equal("\x7f", lwin.dir_entry[1].name); assert_string_equal("\x80", lwin.dir_entry[2].name); assert_string_equal("\x81", lwin.dir_entry[3].name); assert_string_equal("\xff", lwin.dir_entry[4].name); } TEST(sorting_uses_dcache_for_dirs) { view_teardown(&lwin); view_setup(&lwin); assert_success(stats_init(&cfg)); strcpy(lwin.curr_dir, TEST_DATA_PATH); set_file_list(&lwin, FT_DIR, "read", "rename", NULL); view_set_sort(lwin.sort, SK_BY_SIZE, SK_NONE); assert_success(dcache_set_at(TEST_DATA_PATH "/read", 1, 10, DCACHE_UNKNOWN)); assert_success(dcache_set_at(TEST_DATA_PATH "/rename", 2, 100, DCACHE_UNKNOWN)); sort_view(&lwin); assert_string_equal("read", lwin.dir_entry[0].name); assert_string_equal("rename", lwin.dir_entry[1].name); assert_success(dcache_set_at(TEST_DATA_PATH "/rename", 2, 10, DCACHE_UNKNOWN)); assert_success(dcache_set_at(TEST_DATA_PATH "/read", 1, 100, DCACHE_UNKNOWN)); sort_view(&lwin); assert_string_equal("rename", lwin.dir_entry[0].name); assert_string_equal("read", lwin.dir_entry[1].name); } TEST(nitems_sorting_works) { view_teardown(&lwin); view_setup(&lwin); assert_success(stats_init(&cfg)); strcpy(lwin.curr_dir, TEST_DATA_PATH); set_file_list(&lwin, FT_DIR, "read", "rename", "various-sizes", NULL); view_set_sort(lwin.sort, SK_BY_NITEMS, SK_NONE); sort_view(&lwin); assert_string_equal("rename", lwin.dir_entry[0].name); assert_string_equal("read", lwin.dir_entry[1].name); assert_string_equal("various-sizes", lwin.dir_entry[2].name); } TEST(groups_sorting_works) { view_teardown(&lwin); view_setup(&lwin); assert_success(stats_init(&cfg)); strcpy(lwin.curr_dir, TEST_DATA_PATH); set_file_list(&lwin, FT_REG, "1-done", "10-bla-todo-edit", "11-todo-publish", "2-todo-replace", "3-done", "4-todo-edit", "5-todo-publish", NULL); update_string(&lwin.sort_groups, "-(done|todo).*"); if(lwin.primary_group_set) { regfree(&lwin.primary_group); } (void)regcomp(&lwin.primary_group, "-(done|todo).*", REG_EXTENDED | REG_ICASE); lwin.primary_group_set = 1; /* Ascending sorting. */ view_set_sort(lwin.sort, SK_BY_GROUPS, SK_BY_NAME); sort_view(&lwin); assert_string_equal("1-done", lwin.dir_entry[0].name); assert_string_equal("3-done", lwin.dir_entry[1].name); assert_string_equal("2-todo-replace", lwin.dir_entry[2].name); assert_string_equal("4-todo-edit", lwin.dir_entry[3].name); assert_string_equal("5-todo-publish", lwin.dir_entry[4].name); assert_string_equal("10-bla-todo-edit", lwin.dir_entry[5].name); assert_string_equal("11-todo-publish", lwin.dir_entry[6].name); /* Descending sorting. */ view_set_sort(lwin.sort, -SK_BY_GROUPS, SK_BY_NAME); sort_view(&lwin); assert_string_equal("2-todo-replace", lwin.dir_entry[0].name); assert_string_equal("4-todo-edit", lwin.dir_entry[1].name); assert_string_equal("5-todo-publish", lwin.dir_entry[2].name); assert_string_equal("10-bla-todo-edit", lwin.dir_entry[3].name); assert_string_equal("11-todo-publish", lwin.dir_entry[4].name); assert_string_equal("1-done", lwin.dir_entry[5].name); assert_string_equal("3-done", lwin.dir_entry[6].name); } TEST(global_groups_sorts_entries_list) { update_string(&lwin.sort_groups_g, "([0-9])"); if(lwin.primary_group_set) { regfree(&lwin.primary_group); } (void)regcomp(&lwin.primary_group, "([a-z])", REG_EXTENDED | REG_ICASE); lwin.primary_group_set = 1; dir_entry_t entry_list[] = { { .name = "a1" }, { .name = "b0" } }; entries_t entries = { entry_list, 2 }; view_set_sort(lwin.sort_g, SK_BY_GROUPS, SK_BY_NAME); sort_entries(&lwin, entries); assert_int_equal(2, entries.nentries); assert_string_equal("b0", entries.entries[0].name); assert_string_equal("a1", entries.entries[1].name); } /* Not a proper collation, but a sensible ordering that is consistent whether * case is ignored or not. */ TEST(case_sensitive_unicode_sorting, IF(utf8_locale)) { view_teardown(&lwin); view_setup(&lwin); /* Hex-coded names are öäüßÖÄÜ written with and without compount * characters. */ const char *compounds = "\x6f\xcc\x88\x61\xcc\x88\x75\xcc\x88\xc3\x9f\x4f\xcc\x88\x41\xcc\x88\x55" "\xcc\x88"; const char *non_compounds = "\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f\xc3\x96\xc3\x84\xc3\x9c"; set_file_list(&lwin, FT_REG, "a", "A", compounds, non_compounds, "ß", "Ö", "ö", "p", NULL); view_set_sort(lwin.sort, SK_BY_NAME, SK_NONE); sort_view(&lwin); assert_string_equal("A", lwin.dir_entry[0].name); assert_string_equal("Ö", lwin.dir_entry[1].name); assert_string_equal("a", lwin.dir_entry[2].name); assert_string_equal("ö", lwin.dir_entry[3].name); assert_string_equal(compounds, lwin.dir_entry[4].name); assert_string_equal(non_compounds, lwin.dir_entry[5].name); assert_string_equal("p", lwin.dir_entry[6].name); } /* Not a proper collation, but a sensible ordering that is consistent whether * case is ignored or not. */ TEST(case_insensitive_unicode_sorting, IF(utf8_locale)) { view_teardown(&lwin); view_setup(&lwin); /* Hex-coded names are öäüßÖÄÜ written with and without compount * characters. */ const char *compounds = "\x6f\xcc\x88\x61\xcc\x88\x75\xcc\x88\xc3\x9f\x4f\xcc\x88\x41\xcc\x88\x55" "\xcc\x88"; const char *non_compounds = "\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f\xc3\x96\xc3\x84\xc3\x9c"; set_file_list(&lwin, FT_REG, "a", "A", compounds, non_compounds, "ß", "Ö", "ö", "p", NULL); view_set_sort(lwin.sort, SK_BY_INAME, SK_NONE); sort_view(&lwin); assert_string_equal("A", lwin.dir_entry[0].name); assert_string_equal("a", lwin.dir_entry[1].name); assert_string_equal("Ö", lwin.dir_entry[2].name); assert_string_equal("ö", lwin.dir_entry[3].name); assert_string_equal(compounds, lwin.dir_entry[4].name); assert_string_equal(non_compounds, lwin.dir_entry[5].name); assert_string_equal("p", lwin.dir_entry[6].name); } /* Not a proper collation, but a sensible ordering that is consistent whether * case is ignored or not. */ TEST(case_insensitive_unicode_sorting_for_exts, IF(utf8_locale)) { view_teardown(&lwin); view_setup(&lwin); set_file_list(&lwin, FT_REG, "x.o", "y.Ö", "z.ö", NULL); view_set_sort(lwin.sort, SK_BY_FILEEXT, SK_NONE); sort_view(&lwin); assert_string_equal("y.Ö", lwin.dir_entry[0].name); assert_string_equal("x.o", lwin.dir_entry[1].name); assert_string_equal("z.ö", lwin.dir_entry[2].name); } TEST(custom_view_is_sorted_by_short_paths, IF(on_case_sensitive_fs)) { make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), /*base=*/".", /*sub=*/"", /*cwd=*/NULL); create_dir(SANDBOX_PATH "/D"); create_file(SANDBOX_PATH "/D/f"); create_dir(SANDBOX_PATH "/d"); create_file(SANDBOX_PATH "/d/f"); /* flist_custom_finish() does actual sorting. */ view_set_sort(lwin.sort, SK_BY_INAME, SK_NONE); flist_custom_start(&lwin, "test"); flist_custom_add(&lwin, SANDBOX_PATH "/d/f"); flist_custom_add(&lwin, SANDBOX_PATH "/D/f"); assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0); assert_int_equal(2, lwin.list_rows); assert_string_equal("f", lwin.dir_entry[0].name); assert_string_ends_with("/D", lwin.dir_entry[0].origin); assert_string_equal("f", lwin.dir_entry[1].name); assert_string_ends_with("/d", lwin.dir_entry[1].origin); remove_file(SANDBOX_PATH "/d/f"); remove_file(SANDBOX_PATH "/D/f"); remove_dir(SANDBOX_PATH "/d"); remove_dir(SANDBOX_PATH "/D"); } #ifndef _WIN32 TEST(inode_sorting_works) { view_teardown(&lwin); view_setup(&lwin); assert_success(stats_init(&cfg)); strcpy(lwin.curr_dir, TEST_DATA_PATH); set_file_list(&lwin, FT_REG, "read", "rename", "various-sizes", NULL); lwin.dir_entry[0].inode = 10; lwin.dir_entry[1].inode = 5; lwin.dir_entry[2].inode = 7; view_set_sort(lwin.sort, SK_BY_INODE, SK_NONE); sort_view(&lwin); assert_string_equal("rename", lwin.dir_entry[0].name); assert_string_equal("various-sizes", lwin.dir_entry[1].name); assert_string_equal("read", lwin.dir_entry[2].name); } #endif static void set_file_list(view_t *view, FileType def_ftype, ...) { va_list ap; va_start(ap, def_ftype); va_list aq; va_copy(aq, ap); int count = 0; while(va_arg(ap, const char *) != NULL) { ++count; } va_end(ap); view->list_rows = count; view->dir_entry = dynarray_cextend(NULL, view->list_rows*sizeof(*view->dir_entry)); int i; for(i = 0; i < count; ++i) { view->dir_entry[i].name = strdup(va_arg(aq, const char *)); view->dir_entry[i].type = def_ftype; view->dir_entry[i].origin = view->curr_dir; #ifndef _WIN32 view->dir_entry[i].inode = 1 + i; #endif } va_end(aq); } /* Checks that sandbox directory is located on a file-system that allows files * that differ only by character case. Returns non-zero if so. */ static int on_case_sensitive_fs(void) { /* Use of SANDBOX_PATH prevents this function from being in test utilities. */ int result = (os_mkdir(SANDBOX_PATH "/tEsT", 0700) == 0) && (os_rmdir(SANDBOX_PATH "/test") != 0); (void)os_rmdir(SANDBOX_PATH "/tEsT"); return result; } /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 filetype=c : */