spiegel-keyman/core/tests/unit/api/state_api.tests.cpp

385 lines
14 KiB
C++

/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Created by Tim Eves on 2018-10-30
*
* Keyman Core - state API unit tests
*/
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include "keyman_core.h"
#include "path.hpp"
#include "state.hpp"
#include "mock/mock_processor.hpp"
#include "../helpers/action_items.hpp"
#include "../helpers/core_test_helpers.h"
#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
{
km_core_option_item test_env_opts[] =
{
{u"hello", u"world", 0},
KM_CORE_OPTIONS_END
};
constexpr km_core_option_item const expected_persist_opt = {
u"test_keyboard_option",
u"F2 pressed test save.",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const clone_persist_opt = {
u"__test_clone",
u"Not in original",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const test_point_3_opt = {
u"__test_point_3",
u"F3 pressed test save 1.",
KM_CORE_OPT_KEYBOARD
};
constexpr km_core_option_item const test_point_4_opt = {
u"__test_point_4",
u"F3 pressed test save 2.",
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;
}
};
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;
}
} // namespace
TEST(StateApiTests, TestStateApi) {
km_core_keyboard * test_kb = nullptr;
km_core_state * test_state = nullptr,
* test_clone = nullptr,
* test_clone_2 = nullptr;
test_kb = (km_core_keyboard *)new km::core::mock_processor(km::core::path("dummy.mock"));
// Simple sanity tests.
ASSERT_STATUS_OK(km_core_state_create(test_kb, test_env_opts, &test_state));
ASSERT_STATUS_OK(km_core_state_clone(test_state, &test_clone));
// Check sub objects have been copied and not shared.
ASSERT_NE(km_core_state_context(test_state), km_core_state_context(test_clone));
size_t n_actions = 0;
ASSERT_FALSE(km_core_state_action_items(test_state, &n_actions) == nullptr
&& n_actions != 0);
// 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;
ASSERT_STATUS_OK(context_items_from_utf16(u"Hello 😁", &citems));
ASSERT_STATUS_OK(km_core_context_set(km_core_state_context(test_state), citems));
km_core_context_items_dispose(citems);
ASSERT_EQ(km_core_context_length(km_core_state_context(test_state)), 7);
ASSERT_EQ(km_core_context_length(km_core_state_context(test_clone)), 0);
// Overwrite some data.
km_core_option_item new_opt[] = {
{u"hello", u"globe", KM_CORE_OPT_ENVIRONMENT},
KM_CORE_OPTIONS_END};
ASSERT_STATUS_OK(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.
ASSERT_FALSE(attrs->current - attrs->age > KM_CORE_LIB_CURRENT
|| attrs->current < KM_CORE_LIB_CURRENT);
ASSERT_FALSE(attrs->max_context < 16);
DISABLE_WARNING_POP
ASSERT_STATUS_OK(km_core_process_event(test_state, KM_CORE_VKEY_S,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
ASSERT_TRUE(km::tests::action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}}, {KM_CORE_IT_END}}));
ASSERT_STATUS_OK(km_core_process_event(test_state, KM_CORE_VKEY_I,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
ASSERT_TRUE(km::tests::action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('I')}}, {KM_CORE_IT_END}}));
ASSERT_STATUS_OK(km_core_process_event(test_state, KM_CORE_VKEY_L, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
ASSERT_TRUE(km::tests::action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('l')}}, {KM_CORE_IT_END}}));
ASSERT_STATUS_OK(km_core_process_event(test_state, KM_CORE_VKEY_BKSP, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
ASSERT_TRUE(km::tests::action_items(test_state, {{KM_CORE_IT_BACK, {0,}, {0}}, {KM_CORE_IT_END}}));
ASSERT_STATUS_OK(km_core_process_event(test_state, KM_CORE_VKEY_L,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
ASSERT_TRUE(km::tests::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;
ASSERT_STATUS_OK(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;
ASSERT_TRUE(km::tests::action_items(test_state, {action, {KM_CORE_IT_END}}));
// 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();
ASSERT_TRUE (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
));
ASSERT_TRUE (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
));
// Add two actions before cloning the state again
ASSERT_STATUS_OK(km_core_process_event(test_state, KM_CORE_VKEY_F3, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
ASSERT_STATUS_OK(km_core_state_clone(test_state, &test_clone_2));
// Now put an option in the test_clone_2 state only
km_core_action_item action_clone = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_clone.option = &clone_persist_opt;
if (test_clone_2->actions().back().type == KM_CORE_IT_END) {
test_clone_2->actions().pop_back();
}
km_core_state_queue_action_items(test_clone_2, &action_clone);
test_clone_2->actions().commit();
km_core_action_item action_tp3 = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_tp3.option = &test_point_3_opt;
km_core_action_item action_tp4 = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action_tp4.option = &test_point_4_opt;
ASSERT_TRUE(km::tests::action_items(test_state, {action_tp3, action_tp4, {KM_CORE_IT_END}}));
// Check that test_clone_2 has the same persisted options plus the extra queued option.
ASSERT_TRUE(km::tests::action_items(test_clone_2, {action_tp3, action_tp4, action_clone, {KM_CORE_IT_END}}));
// Destroy them
km_core_state_dispose(test_state);
km_core_state_dispose(test_clone);
km_core_state_dispose(test_clone_2);
km_core_keyboard_dispose(test_kb);
}