From 26092456d48dc277e644f959a5cce723c2b7ea6e Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Fri, 30 Jul 2021 19:51:42 -0500 Subject: [PATCH] Add CMake enumeration of low-level fish_tests.cpp tests Instead of compiling `fish_tests.cpp` dynamically with weakly-linked symbols and asking it to print the list of all available tests, we use a magic string `#define`'d as a no-op to allow CMake to regex search for matching test groups. This speeds up configuration somewhat (by not compiling anything), but more importantly, it's much less brittle and doesn't involve and linker dark magic. There's of course still no getting around the fact that it's really ugly. --- cmake/Tests.cmake | 60 ++++------------------ src/fish_tests.cpp | 123 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 49 deletions(-) diff --git a/cmake/Tests.cmake b/cmake/Tests.cmake index 80b3ae79a..1d543d944 100644 --- a/cmake/Tests.cmake +++ b/cmake/Tests.cmake @@ -17,24 +17,17 @@ fish_link_deps_and_sign(fish_tests) # The "test" directory. set(TEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/test) -# HACK: CMake is a configuration tool, not a build system. However, until CMake adds a way to -# dynamically discover tests, our options are either this or resorting to sed/awk to parse the low -# level tests source file to get the list of individual tests. Or to split each test into its own -# source file. -execute_process( - COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_SOURCE_DIR}/src/fish_tests.cpp - -o ${CMAKE_BINARY_DIR}/fish_tests_list - -I ${CMAKE_CURRENT_BINARY_DIR} # for config.h - -lpthread - # Strip actual dependency on fish code - -Wl,-undefined,dynamic_lookup,--unresolved-symbols=ignore-all - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} -) -execute_process( - COMMAND ./fish_tests_list --list - OUTPUT_FILE low_level_tests.txt - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} -) +# CMake doesn't really support dynamic test discovery where a test harness is executed to list the +# tests it contains, making fish_tests.cpp's tests opaque to CMake (whereas littlecheck tests can be +# enumerated from the filesystem). We used to compile fish_tests.cpp without linking against +# anything (-Wl,-undefined,dynamic_lookup,--unresolved-symbols=ignore-all) to get it to print its +# tests at configuration time, but that's a little too much dark CMake magic. +# +# We now identify tests by checking against a magic regex that's #define'd as a no-op C-side. +file(READ "${CMAKE_SOURCE_DIR}/src/fish_tests.cpp" FISH_TESTS_CPP) +string(REGEX MATCHALL "TEST_GROUP\\( *\"([^\"]+)\"" "LOW_LEVEL_TESTS" "${FISH_TESTS_CPP}") +string(REGEX REPLACE "TEST_GROUP\\( *\"([^\"]+)\"" "\\1" "LOW_LEVEL_TESTS" "${LOW_LEVEL_TESTS}") +list(REMOVE_DUPLICATES LOW_LEVEL_TESTS) # The directory into which fish is installed. set(TEST_INSTALL_DIR ${TEST_DIR}/buildroot) @@ -76,37 +69,6 @@ add_custom_target(tests_buildroot_target ${TEST_ROOT_DIR} DEPENDS fish fish_test_helper) -if(NOT FISH_IN_TREE_BUILD) - # We need to symlink share/functions for the tests. - # This should be simplified. - add_custom_target(symlink_functions - COMMAND ${CMAKE_COMMAND} -E create_symlink - ${CMAKE_CURRENT_SOURCE_DIR}/share/functions - ${CMAKE_CURRENT_BINARY_DIR}/share/functions) - add_dependencies(tests_buildroot_target symlink_functions) -else() - add_custom_target(symlink_functions) -endif() - -# Prep the environment for running the unit tests. -add_custom_target(test_prep - # Add directories hard-coded into the tests - COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/data - COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_DIR}/data - COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/temp - COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_DIR}/temp - - # Add the XDG_* directories - COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/xdg_data - COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_DIR}/xdg_data - COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/xdg_config - COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_DIR}/xdg_config - COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/xdg_runtime - COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_DIR}/xdg_runtime - - DEPENDS tests_buildroot_target tests_dir - USES_TERMINAL) - foreach(LTEST ${LOW_LEVEL_TESTS}) add_test( NAME ${LTEST} diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index dd13fbd1c..955ccd9b9 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -6647,6 +6647,129 @@ void termsize_tester_t::test() { do_test(ts2.updating(parser) == *stubby_termsize); } +// typedef void (test_entry_point_t)(); +using test_entry_point_t = void (*)(); +struct test_t { + const char *group; + std::function test; + bool opt_in = false; + + test_t(const char *group, test_entry_point_t test, bool opt_in = false) + : group(group), test(test), opt_in(opt_in) {} +}; + +struct test_comparator_t { + // template + int operator()(const test_t &lhs, const test_t &rhs) { return strcmp(lhs.group, rhs.group); } +}; + +// This magic string is required for CMake to pick up the list of tests +#define TEST_GROUP(x) x +static const test_t s_tests[]{ + {TEST_GROUP("utility_functions"), test_utility_functions}, + {TEST_GROUP("string_split"), test_split_string_tok}, + {TEST_GROUP("wwrite_to_fd"), test_wwrite_to_fd}, + {TEST_GROUP("env_vars"), test_env_vars}, + {TEST_GROUP("env"), test_env_snapshot}, + {TEST_GROUP("str_to_num"), test_str_to_num}, + {TEST_GROUP("enum"), test_enum_set}, + {TEST_GROUP("enum"), test_enum_array}, + {TEST_GROUP("highlighting"), test_highlighting}, + {TEST_GROUP("new_parser_ll2"), test_new_parser_ll2}, + {TEST_GROUP("new_parser_fuzzing"), test_new_parser_fuzzing}, + {TEST_GROUP("new_parser_correctness"), test_new_parser_correctness}, + {TEST_GROUP("new_parser_ad_hoc"), test_new_parser_ad_hoc}, + {TEST_GROUP("new_parser_errors"), test_new_parser_errors}, + {TEST_GROUP("error_messages"), test_error_messages}, + {TEST_GROUP("escape"), test_unescape_sane}, + {TEST_GROUP("escape"), test_escape_crazy}, + {TEST_GROUP("escape"), test_escape_quotes}, + {TEST_GROUP("format"), test_format}, + {TEST_GROUP("convert"), test_convert}, + {TEST_GROUP("convert"), test_convert_private_use}, + {TEST_GROUP("convert_ascii"), test_convert_ascii}, + {TEST_GROUP("perf_convert_ascii"), perf_convert_ascii, true}, + {TEST_GROUP("convert_nulls"), test_convert_nulls}, + {TEST_GROUP("tokenizer"), test_tokenizer}, + {TEST_GROUP("fd_monitor"), test_fd_monitor}, + {TEST_GROUP("iothread"), test_iothread}, + {TEST_GROUP("pthread"), test_pthread}, + {TEST_GROUP("debounce"), test_debounce}, + {TEST_GROUP("debounce"), test_debounce_timeout}, + {TEST_GROUP("parser"), test_parser}, + {TEST_GROUP("cancellation"), test_cancellation}, + {TEST_GROUP("indents"), test_indents}, + {TEST_GROUP("utf8"), test_utf8}, + {TEST_GROUP("feature_flags"), test_feature_flags}, + {TEST_GROUP("escape_sequences"), test_escape_sequences}, + {TEST_GROUP("pcre2_escape"), test_pcre2_escape}, + {TEST_GROUP("lru"), test_lru}, + {TEST_GROUP("expand"), test_expand}, + {TEST_GROUP("expand"), test_expand_overflow}, + {TEST_GROUP("fuzzy_match"), test_fuzzy_match}, + {TEST_GROUP("ifind"), test_ifind}, + {TEST_GROUP("ifind_fuzzy"), test_ifind_fuzzy}, + {TEST_GROUP("abbreviations"), test_abbreviations}, + {TEST_GROUP("builtin_test"), test_test}, + {TEST_GROUP("wcstod"), test_wcstod}, + {TEST_GROUP("dup2s"), test_dup2s}, + {TEST_GROUP("dup2s"), test_dup2s_fd_for_target_fd}, + {TEST_GROUP("path"), test_path}, + {TEST_GROUP("pager_navigation"), test_pager_navigation}, + {TEST_GROUP("pager_layout"), test_pager_layout}, + {TEST_GROUP("word_motion"), test_word_motion}, + {TEST_GROUP("is_potential_path"), test_is_potential_path}, + {TEST_GROUP("colors"), test_colors}, + {TEST_GROUP("complete"), test_complete}, + {TEST_GROUP("autoload"), test_autoload}, + {TEST_GROUP("input"), test_input}, + {TEST_GROUP("line_iterator"), test_line_iterator}, + {TEST_GROUP("undo"), test_undo}, + {TEST_GROUP("universal"), test_universal}, + {TEST_GROUP("universal"), test_universal_output}, + {TEST_GROUP("universal"), test_universal_parsing}, + {TEST_GROUP("universal"), test_universal_parsing_legacy}, + {TEST_GROUP("universal"), test_universal_callbacks}, + {TEST_GROUP("universal"), test_universal_formats}, + {TEST_GROUP("universal"), test_universal_ok_to_save}, + {TEST_GROUP("notifiers"), test_universal_notifiers}, + {TEST_GROUP("wait_handles"), test_wait_handles}, + {TEST_GROUP("completion_insertions"), test_completion_insertions}, + {TEST_GROUP("autosuggestion_ignores"), test_autosuggestion_ignores}, + {TEST_GROUP("autosuggestion_combining"), test_autosuggestion_combining}, + {TEST_GROUP("autosuggest_suggest_special"), test_autosuggest_suggest_special}, + {TEST_GROUP("history"), history_tests_t::test_history}, + {TEST_GROUP("history_merge"), history_tests_t::test_history_merge}, + {TEST_GROUP("history_paths"), history_tests_t::test_history_path_detection}, + {TEST_GROUP("history_races"), history_tests_t::test_history_races}, + {TEST_GROUP("history_formats"), history_tests_t::test_history_formats}, + {TEST_GROUP("string"), test_string}, + {TEST_GROUP("illegal_command_exit_code"), test_illegal_command_exit_code}, + {TEST_GROUP("maybe"), test_maybe}, + {TEST_GROUP("layout_cache"), test_layout_cache}, + {TEST_GROUP("prompt"), test_prompt_truncation}, + {TEST_GROUP("normalize"), test_normalize_path}, + {TEST_GROUP("dirname"), test_dirname_basename}, + {TEST_GROUP("topics"), test_topic_monitor}, + {TEST_GROUP("topics"), test_topic_monitor_torture}, + {TEST_GROUP("pipes"), test_pipes}, + {TEST_GROUP("fd_event"), test_fd_event_signaller}, + {TEST_GROUP("timer_format"), test_timer_format}, + {TEST_GROUP("termsize"), termsize_tester_t::test}, + {TEST_GROUP("killring"), test_killring}, +}; + +void list_tests() { + std::set groups; + for (const auto &test : s_tests) { + groups.insert(test.group); + } + + for (const auto &group : groups) { + std::fprintf(stdout, "%s\n", group.c_str()); + } +} + /// Main test. int main(int argc, char **argv) { UNUSED(argc);