/* Copyright: © 2018 SIL International. Description: Tests for the context API family of functions. Create Date: 30 Oct 2018 Authors: Tim Eves (TSE) */ #include #include #include #include #include "keyman_core.h" #include "path.hpp" #include "state.hpp" #include "action_items.hpp" #include "mock/mock_processor.hpp" #include #if defined(__GNUC__) || defined(__clang__) #define PRAGMA(X) _Pragma(#X) #define DISABLE_WARNING_PUSH PRAGMA(GCC diagnostic push) #define DISABLE_WARNING_POP PRAGMA(GCC diagnostic pop) #define DISABLE_WARNING(W) PRAGMA(GCC diagnostic ignored #W) #define DISABLE_WARNING_TYPE_LIMITS DISABLE_WARNING(-Wtype-limits) #else #define DISABLE_WARNING_PUSH #define DISABLE_WARNING_POP #define DISABLE_WARNING_TYPE_LIMITS #endif namespace { std::string get_json_doc(km_core_state const& state) { size_t sz = 0; try_status(km_core_state_to_json(&state, nullptr, &sz)); std::string buf(sz-1, 0); try_status(km_core_state_to_json(&state, &buf[0], &sz)); return buf; } inline bool action_options_equal(km_core_option_item const * lhs, km_core_option_item const * rhs) { if (lhs == rhs) return true; if (!lhs || !rhs) return false; while (lhs->key && rhs->key) { if (lhs->scope != rhs->scope) return false; if (std::u16string(lhs->key) != std::u16string(rhs->key)) return false; if (std::u16string(lhs->value) != std::u16string(rhs->value)) return false; ++lhs; ++rhs; } return lhs->key == nullptr && rhs->key == nullptr; } inline bool expect_action_struct( km_core_actions const & actions, unsigned int expected_code_points_to_delete, km_core_usv const * expected_output, km_core_option_item const * expected_persist_options, km_core_bool expected_do_alert, km_core_bool expected_emit_keystroke, km_core_caps_state expected_new_caps_lock_state, km_core_usv const * expected_deleted_context ) { bool all_passed = true; std::cout << "\n=== Comparing action_struct fields ===" << std::endl; // code_points_to_delete std::cout << "code_points_to_delete: " << actions.code_points_to_delete << " (expected: " << expected_code_points_to_delete << ")"; if (actions.code_points_to_delete != expected_code_points_to_delete) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } // output std::cout << "output: " << (actions.output ? std::u32string(actions.output) : U"(null)") << " expected: " << (expected_output ? std::u32string(expected_output) : U"(null)") << std::endl; bool output_equal = false; if (expected_output == actions.output) { // nullptr or same pointer output_equal = true; } else if (!expected_output || !actions.output) { output_equal = false; } else if (expected_output && actions.output) { if (std::u32string(actions.output) != std::u32string(expected_output)) { output_equal = false; } else { output_equal = true; } } if (!output_equal) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } // persist_options std::cout << "persist_options comparison:" << std::endl; if (!action_options_equal(actions.persist_options, expected_persist_options)) { std::cout << " actual: "; if (actions.persist_options) { for (auto opt = actions.persist_options; opt->scope; ++opt) { std::cout << "[scope=" << opt->scope << ", key=" << opt->key << ", value=" << opt->value << "] "; } } else { std::cout << "(null)"; } std::cout << std::endl; std::cout << " expected: "; if (expected_persist_options) { for (auto opt = expected_persist_options; opt->key; ++opt) { std::cout << "[scope=" << opt->scope << ", key=" << opt->key << ", value=" << opt->value << "] "; } } else { std::cout << "(null)"; } std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } // do_alert std::cout << "do_alert: " << (int)actions.do_alert << " (expected: " << (int)expected_do_alert << ")"; if (actions.do_alert != expected_do_alert) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } // emit_keystroke std::cout << "emit_keystroke: " << (int)actions.emit_keystroke << " (expected: " << (int)expected_emit_keystroke << ")"; if (actions.emit_keystroke != expected_emit_keystroke) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } // new_caps_lock_state std::cout << "new_caps_lock_state: " << (int)actions.new_caps_lock_state << " (expected: " << (int)expected_new_caps_lock_state << ")"; if (actions.new_caps_lock_state != expected_new_caps_lock_state) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } // deleted_context std::cout << "deleted_context: " << (actions.deleted_context ? std::u32string(actions.deleted_context) : U"(null)") << " (expected: " << (expected_deleted_context ? std::u32string(expected_deleted_context) : U"(null)") << ")"; if (expected_deleted_context != actions.deleted_context) { if (std::u32string(actions.deleted_context) != std::u32string(expected_deleted_context)) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } } else { std::cout << " [PASS]" << std::endl; } bool deleted_equal = false; if (expected_deleted_context == actions.deleted_context) { // nullptr or same pointer deleted_equal = true; } else if (!expected_deleted_context || !actions.deleted_context) { deleted_equal = false; } else if (expected_deleted_context && actions.deleted_context) { if (std::u32string(actions.deleted_context) != std::u32string(expected_deleted_context)) { deleted_equal = false; } else { deleted_equal = true; } } if (!deleted_equal) { std::cout << " [FAIL]" << std::endl; all_passed = false; } else { std::cout << " [PASS]" << std::endl; } std::cout << "=== End comparison ===" << std::endl << std::endl; return all_passed; } km_core_option_item test_env_opts[] = { {u"hello", u"world", 0}, KM_CORE_OPTIONS_END }; constexpr char const *doc1_expected = u8"\ {\n\ \"$schema\" : \"keyman/core/docs/introspection.schema\",\n\ \"keyboard\" : {\n\ \"id\" : \"dummy\",\n\ \"version\" : \"3.145\",\n\ \"rules\" : []\n\ },\n\ \"context\" : [\n\ \"H\",\n\ \"e\",\n\ \"l\",\n\ \"l\",\n\ \"o\",\n\ \" \",\n\ \"😁\",\n\ \"S\",\n\ \"I\",\n\ \"L\"\n\ ],\n\ \"actions\" : [\n\ { \"persist\" : { \"keyboard\" : { \"__test_point\" : \"F2 pressed test save.\" } } }\n\ ]\n\ }\n"; constexpr char const *doc2_expected = u8"\ {\n\ \"$schema\" : \"keyman/core/docs/introspection.schema\",\n\ \"keyboard\" : {\n\ \"id\" : \"dummy\",\n\ \"version\" : \"3.145\",\n\ \"rules\" : []\n\ },\n\ \"context\" : [],\n\ \"actions\" : []\n\ }\n"; constexpr km_core_option_item const expected_persist_opt = { u"__test_point", u"F2 pressed test save.", KM_CORE_OPT_KEYBOARD }; extern "C" { uint8_t test_imx_callback(km_core_state *state, uint32_t imx_id, void *callback_object){ // does nothing; return 1; } }; } // namespace int main(int argc, char * argv[]) { auto arg_color = std::string(argc > 1 ? argv[1] : "") == "--color"; console_color::enabled = console_color::isaterminal() || arg_color; km_core_keyboard * test_kb = nullptr; km_core_state * test_state = nullptr, * test_clone = nullptr; test_kb = (km_core_keyboard *)new km::core::mock_processor(km::core::path("dummy.mock")); // Simple sanity tests. try_status(km_core_state_create(test_kb, test_env_opts, &test_state)); try_status(km_core_state_clone(test_state, &test_clone)); // Check sub objects have been copied and not shared. if (km_core_state_context(test_state) == km_core_state_context(test_clone)) return __LINE__; size_t n_actions = 0; if (km_core_state_action_items(test_state, &n_actions) == nullptr && n_actions != 0) return __LINE__; // Check registering platform engine callback km_core_state_imx_register_callback(test_state, test_imx_callback, nullptr); km_core_state_imx_deregister_callback(test_state); // Lets add data and do some basic checks of options and km_core_context km_core_context_item *citems = nullptr; try_status(context_items_from_utf16(u"Hello 😁", &citems)); try_status(km_core_context_set(km_core_state_context(test_state), citems)); km_core_context_items_dispose(citems); if(km_core_context_length(km_core_state_context(test_state)) != 7) return __LINE__; if(km_core_context_length(km_core_state_context(test_clone)) != 0) return __LINE__; // Overwrite some data. km_core_option_item new_opt[] = { {u"hello", u"globe", KM_CORE_OPT_ENVIRONMENT}, KM_CORE_OPTIONS_END}; try_status(km_core_state_options_update(test_clone, new_opt)); // Test the engine auto attrs = km_core_get_engine_attrs(test_state); DISABLE_WARNING_PUSH DISABLE_WARNING_TYPE_LIMITS // Check the lib supplies our required interface. if (attrs->current - attrs->age > KM_CORE_LIB_CURRENT || attrs->current < KM_CORE_LIB_CURRENT) return __LINE__; if (attrs->max_context < 16) return __LINE__; DISABLE_WARNING_POP try_status(km_core_process_event(test_state, KM_CORE_VKEY_S, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT)); test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}}, {KM_CORE_IT_END}})); try_status(km_core_process_event(test_state, KM_CORE_VKEY_I, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT)); test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('I')}}, {KM_CORE_IT_END}})); try_status(km_core_process_event(test_state, KM_CORE_VKEY_L, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT)); test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('l')}}, {KM_CORE_IT_END}})); try_status(km_core_process_event(test_state, KM_CORE_VKEY_BKSP, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT)); test_assert(action_items(test_state, {{KM_CORE_IT_BACK, {0,}, {0}}, {KM_CORE_IT_END}})); try_status(km_core_process_event(test_state, KM_CORE_VKEY_L, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT)); test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('L')}}, {KM_CORE_IT_END}})); // Without the calling `km_core_state_context_set_if_needed` action struct has a delete for 'L'? // Issue raised to investigate further: https://github.com/keymanapp/keyman/issues/15962 km_core_cu const *state_context = get_context_as_string(km_core_state_context(test_state)); km_core_state_context_set_if_needed(test_state, state_context); delete [] state_context; try_status(km_core_process_event(test_state, KM_CORE_VKEY_F2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT)); km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, }; action.option = &expected_persist_opt; test_assert(action_items(test_state, {action, {KM_CORE_IT_END}})); // Test debug dump auto doc1 = get_json_doc(*test_state), doc2 = get_json_doc(*test_clone); std::cout << doc1 << std::endl; std::cout << doc2 << std::endl; // These should not be equal. if (doc1 == doc2) return __LINE__; // These should be. if (doc1 != doc1_expected) return __LINE__; if (doc2 != doc2_expected) return __LINE__; // Test the action_struct values for the active and cloned states are // independent and match their respective expected values. const unsigned int expected_state_code_points_to_delete = 0; const km_core_usv * expected_state_output = U""; const km_core_bool expected_state_do_alert = KM_CORE_FALSE; const km_core_bool expected_state_emit_keystroke = KM_CORE_FALSE; const km_core_caps_state expected_state_new_caps_lock_state = KM_CORE_CAPS_UNCHANGED; km_core_option_item expected_options[] = {expected_persist_opt, KM_CORE_OPTIONS_END }; const km_core_usv * expected_deleted_text = U""; // Cloned expected values const unsigned int clone_state_code_points_to_delete = 0; const km_core_usv * clone_state_output = U""; const km_core_bool clone_state_do_alert = KM_CORE_FALSE; const km_core_bool clone_state_emit_keystroke = KM_CORE_FALSE; const km_core_caps_state clone_state_new_caps_lock_state = KM_CORE_CAPS_UNCHANGED; km_core_option_item clone_state_options[] = {KM_CORE_OPTIONS_END}; const km_core_usv * clone_state_deleted_text = nullptr; const auto & state_actions = test_state->action_struct(); const auto & clone_actions = test_clone->action_struct(); test_assert (expect_action_struct(state_actions, expected_state_code_points_to_delete, expected_state_output, expected_options, expected_state_do_alert, expected_state_emit_keystroke, expected_state_new_caps_lock_state, expected_deleted_text )); test_assert (expect_action_struct(clone_actions, clone_state_code_points_to_delete, clone_state_output, clone_state_options, clone_state_do_alert, clone_state_emit_keystroke, clone_state_new_caps_lock_state, clone_state_deleted_text )); // Destroy them km_core_state_dispose(test_state); km_core_state_dispose(test_clone); km_core_keyboard_dispose(test_kb); return 0; }