From 0663fcbeb7645c7ddaccc3d52862afa2b0f02b89 Mon Sep 17 00:00:00 2001 From: Ross Date: Fri, 27 Aug 2021 14:18:14 +1000 Subject: [PATCH] feat(windows): Add unit tests for process actions --- windows/src/engine/keyman32/K32_load.cpp | 3 + windows/src/engine/keyman32/Keyman32.cpp | 2 + .../keyman-engine-tests.vcxproj | 1 + .../kmprocessactionstests.cpp | 209 ++++++++++++++++++ windows/src/global/inc/keymanengine.h | 2 + 5 files changed, 217 insertions(+) create mode 100644 windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp diff --git a/windows/src/engine/keyman32/K32_load.cpp b/windows/src/engine/keyman32/K32_load.cpp index 9233da3b25..febb506616 100644 --- a/windows/src/engine/keyman32/K32_load.cpp +++ b/windows/src/engine/keyman32/K32_load.cpp @@ -86,6 +86,7 @@ BOOL LoadlpKeyboardCore(int i) if (_td->lpActiveKeyboard == &_td->lpKeyboards[i]) _td->lpActiveKeyboard = NULL; // I822 TSF not working if (_td->lpKeyboards[i].lpCoreKeyboardState) { + SendDebugMessageFormat(0, sdmLoad, 0, "LoadlpKeyboardCore: a keyboard km_kbp_state exits without matching keyboard - disposing of state"); km_kbp_state_dispose(_td->lpKeyboards[i].lpCoreKeyboardState); _td->lpKeyboards[i].lpCoreKeyboardState = NULL; } @@ -111,6 +112,8 @@ BOOL LoadlpKeyboardCore(int i) if (err_status != KM_KBP_STATUS_OK) { SendDebugMessageFormat( 0, sdmLoad, 0, "LoadlpKeyboardCore: km_kbp_state_create failed with error status [%d]", err_status); + // Dispose of the keyboard to leave us in a consitent state + ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard); return FALSE; } diff --git a/windows/src/engine/keyman32/Keyman32.cpp b/windows/src/engine/keyman32/Keyman32.cpp index 097543bbb3..c810dfe676 100644 --- a/windows/src/engine/keyman32/Keyman32.cpp +++ b/windows/src/engine/keyman32/Keyman32.cpp @@ -583,6 +583,8 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_ForceKeyboard(PCSTR FileName if (err_status != KM_KBP_STATUS_OK) { SendDebugMessageFormat( 0, sdmGlobal, 0, "Keyman_ForceKeyboard Core: km_kbp_state_create failed with error status [%d]", err_status); + // Dispose of the keyboard to leave us in a consitent state + ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard); return FALSE; } LoadKeyboardOptionsREGCore(_td->lpActiveKeyboard, _td->lpActiveKeyboard->lpCoreKeyboardState); diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj index 4931f7e718..7014ad359a 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj @@ -59,6 +59,7 @@ + Create Create diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp new file mode 100644 index 0000000000..66c5fc3206 --- /dev/null +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp @@ -0,0 +1,209 @@ +#include "pch.h" +#include "kmprocessactions.cpp" + +// Test the Process Actions private functions +// Note: The following actions are not tested KM_KBP_IT_ALERT, KM_KBP_IT_PERSIST_OPT, KM_KBP_IT_EMIT_KEYSTROKE + +// KM_KBP_IT_CHAR - processUnicodeChar + TEST(AITIP, processUnicodeChartest) { + Globals_InitProcess(); + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR *expectedContext = L"A"; + const km_kbp_action_item *actionItem; + km_kbp_action_item itemAddChar = { + KM_KBP_IT_CHAR, + { + 0, + }, + {'A'}}; + + processUnicodeChar(&testApp, &itemAddChar); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + km_kbp_usv testSurrogateChar = Uni_SurrogateToUTF32(0xD801, 0xDC37); //𐐷'; + km_kbp_action_item itemAddChar2 = { + KM_KBP_IT_CHAR, + { + 0, + }, + {testSurrogateChar}}; + WCHAR expectedStringSurrogate[] = {'A', 0xD801, 0xDC37, 0}; + processUnicodeChar(&testApp, &itemAddChar2); + contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, &expectedStringSurrogate[0]); + + UninitialiseProcess(FALSE); + Globals_UninitProcess(); +} + +// KM_KBP_IT_MARKER - processMarker Deadkey +TEST(AITIP, processMarkertest) { + Globals_InitProcess(); + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {UC_SENTINEL, CODE_DEADKEY, 2, 0}; + const km_kbp_action_item *actionItem; + uintptr_t marker = 2; + km_kbp_action_item itemAddMarker = { + KM_KBP_IT_MARKER, + { + 0, + }, + {marker}}; + processMarker(&testApp, &itemAddMarker); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + UninitialiseProcess(FALSE); + Globals_UninitProcess(); +} + +// KM_KBP_IT_BACK - processBack +// First test processing a backspace for a deadkey +TEST(AITIP, processBackDeadkeytest) { + Globals_InitProcess(); + Globals_InitThread(); + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {'A', 0}; + + km_kbp_action_item itemAddChar = { + KM_KBP_IT_CHAR, + { + 0, + }, + {'A'}}; + processUnicodeChar(&testApp, &itemAddChar); + uintptr_t marker = 2; + km_kbp_action_item itemAddMarker = { + KM_KBP_IT_MARKER, + { + 0, + }, + {marker}}; + processMarker(&testApp, &itemAddMarker); + km_kbp_action_item itemBackSpace = {KM_KBP_IT_BACK}; + itemBackSpace.backspace.expected_type = KM_KBP_IT_MARKER; + itemBackSpace.backspace.expected_value = marker; + processBack(&testApp, &itemBackSpace); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + UninitialiseProcess(FALSE); + Globals_UninitProcess(); +} + +// KM_KBP_IT_BACK - processBack +// Press Backspace for a normal character +// Also test for Unknown Character +TEST(AITIP, processBackCharactertest) { + Globals_InitProcess(); + Globals_InitThread(); + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {'A', 0}; + WCHAR expectedContextFinal[] = {0}; + + km_kbp_action_item itemAddChar = { + KM_KBP_IT_CHAR, + { + 0, + }, + {'A'}}; + processUnicodeChar(&testApp, &itemAddChar); + itemAddChar.character = 'B'; + processUnicodeChar(&testApp, &itemAddChar); + km_kbp_action_item itemBackSpace = {KM_KBP_IT_BACK}; + itemBackSpace.backspace.expected_type = KM_KBP_IT_CHAR; + itemBackSpace.backspace.expected_value = 'B'; + // backspace + processBack(&testApp, &itemBackSpace); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + // backspace for unknown it should backspace a character + itemBackSpace.type = KM_KBP_BT_UNKNOWN; + processBack(&testApp, &itemBackSpace); + contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContextFinal); + + UninitialiseProcess(FALSE); + Globals_UninitProcess(); +} + +// KM_KBP_IT_BACK - processBack +// Press Backspace for a character doesn't match expected character +// Note currently we don't check for a character match this should be updated +TEST(AITIP, processBackUnexpectedChartest) { + Globals_InitProcess(); + Globals_InitThread(); + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {'A', 0}; + + km_kbp_action_item itemAddChar = { + KM_KBP_IT_CHAR, + { + 0, + }, + {'A'}}; + processUnicodeChar(&testApp, &itemAddChar); + itemAddChar.character = 'C'; + processUnicodeChar(&testApp, &itemAddChar); + km_kbp_action_item itemBackSpace = {KM_KBP_IT_BACK}; + itemBackSpace.backspace.expected_type = KM_KBP_IT_CHAR; + itemBackSpace.backspace.expected_value = 'B'; + // backspace + processBack(&testApp, &itemBackSpace); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + UninitialiseProcess(FALSE); + Globals_UninitProcess(); +} + +// KM_KBP_IT_INVALIDATE_CONTEXT - processInvalidateContext +TEST(AITIP, processInvalidateContextTest) { + Globals_InitProcess(); + Globals_InitThread(); + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {0}; + + km_kbp_action_item itemAddChar = { + KM_KBP_IT_CHAR, + { + 0, + }, + {'A'}}; + processUnicodeChar(&testApp, &itemAddChar); + itemAddChar.character = 'B'; + processUnicodeChar(&testApp, &itemAddChar); + + // A keyboard a state is need to test processInvalidateContext + km_kbp_option_item test_env_opts[] = {{u"hello", u"world", 0}, KM_KBP_OPTIONS_END}; + km_kbp_keyboard *testKB = nullptr; + km_kbp_state *testState = nullptr, *test_clone = nullptr; + km_kbp_path_name dummyPath = L"dummyActions.mock"; + km_kbp_status err_status = km_kbp_keyboard_load(dummyPath, &testKB); + EXPECT_EQ(km_kbp_keyboard_load(dummyPath, &testKB), KM_KBP_STATUS_OK); + EXPECT_EQ(km_kbp_state_create(testKB, test_env_opts, &testState), KM_KBP_STATUS_OK); + + processInvalidateContext(&testApp,testState); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + // dispose keyboard + km_kbp_state_dispose(testState); + km_kbp_keyboard_dispose(testKB); + + UninitialiseProcess(FALSE); + Globals_UninitProcess(); +} diff --git a/windows/src/global/inc/keymanengine.h b/windows/src/global/inc/keymanengine.h index 03d8f3d62d..dd0dfe78f8 100644 --- a/windows/src/global/inc/keymanengine.h +++ b/windows/src/global/inc/keymanengine.h @@ -196,6 +196,8 @@ char* Debug_UnicodeString(PWSTR s, int x = 0); BOOL ShouldDebug_1(); // TSDMState state); +// On failed condition it will always log "message" and return "retValue" +// it will also assert if a debugger is attached for a debug build. #define DebugAssertRetValue(condition, message, retValue) \ { \ if (!(condition)) { \