mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-28 11:17:45 +00:00
feat(common/core/desktop): add debug events
Adds debug events to the kmx processor and corresponding unit tests. This corresponds largely to the way that keyman32 emits debug events, but with one key change: debug events in keyman32 are processed synchronously, whereas in Keyman Core, the full set of debug events will be returned alongside the final action list for the key event. Given this difference, there will likely need to be some more data returned in individual debug events, specifically around intermediate context manipulation, as required for step-by-step debugging. However, that will come in a subsequent PR.
This commit is contained in:
parent
e114f31bba
commit
37bb7b77cf
5 changed files with 489 additions and 77 deletions
|
|
@ -25,6 +25,21 @@
|
|||
*/
|
||||
#define DEBUG_MAX_CONTEXT 80
|
||||
|
||||
/**
|
||||
* The number of stores that can be processed in a rule. This is taken from
|
||||
* MAXSTOREOFFSETS in keyman32 (Windows) and is purely a convenience value.
|
||||
* We can increase it if there is a demonstrated need.
|
||||
*/
|
||||
#define DEBUG_MAX_STORE_OFFSETS 20
|
||||
#define DEBUG_STORE_OFFSETS_SIZE (DEBUG_MAX_STORE_OFFSETS*2+1)
|
||||
|
||||
/**
|
||||
* These modifier flags are used internally in the kmx engine, so will be
|
||||
* exposed only in debugging modifier states.
|
||||
*/
|
||||
#define KM_KBP_MODIFIER_VIRTUALKEY 0x4000
|
||||
#define KM_KBP_MODIFIER_VIRTUALCHARKEY 0x8000
|
||||
|
||||
/**
|
||||
* Input key event data. The `character` member is derived from
|
||||
* a US English key event for vk + modifier_state, and is 0 if
|
||||
|
|
@ -48,11 +63,12 @@ typedef struct {
|
|||
*
|
||||
* Used in all event types except KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_END.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
km_kbp_cp context[DEBUG_MAX_CONTEXT];
|
||||
void *group; // LPGROUP
|
||||
void *store; // LPSTORE
|
||||
void *rule; // LPKEY
|
||||
uint16_t store_offsets[DEBUG_STORE_OFFSETS_SIZE]; // pairs--store, char position, terminated by 0xFFFF // TODO use a better structure here
|
||||
} km_kbp_state_debug_kmx_info;
|
||||
|
||||
/**
|
||||
|
|
@ -61,10 +77,8 @@ typedef struct {
|
|||
typedef struct {
|
||||
uint32_t type; // 32 bits is better optimized than 8 bits
|
||||
uint32_t flags;
|
||||
union {
|
||||
km_kbp_state_debug_key_info key_info;
|
||||
km_kbp_state_debug_kmx_info kmx_info;
|
||||
};
|
||||
km_kbp_state_debug_key_info key_info;
|
||||
km_kbp_state_debug_kmx_info kmx_info;
|
||||
} km_kbp_state_debug_item;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,12 +11,56 @@
|
|||
using namespace km::kbp;
|
||||
using namespace kmx;
|
||||
|
||||
void KMX_DebugItems::push_item(uint8_t type, uint32_t flags, LPGROUP group, LPKEY key, PKMX_WCHAR context) {
|
||||
void KMX_DebugItems::push_item(uint8_t type, uint32_t flags, LPGROUP group, LPKEY key, PKMX_WCHAR context, PKMX_WORD index_stack) {
|
||||
_items->assert_push_entry();
|
||||
km_kbp_state_debug_item item = {type, flags};
|
||||
item.kmx_info.rule = key;
|
||||
u16ncpy(item.kmx_info.context, context, DEBUG_MAX_CONTEXT - 1);
|
||||
if(item.kmx_info.rule && index_stack) {
|
||||
this->fill_store_offsets(&item.kmx_info, index_stack);
|
||||
}
|
||||
|
||||
if(context != nullptr) {
|
||||
u16ncpy(item.kmx_info.context, context, DEBUG_MAX_CONTEXT - 1);
|
||||
}
|
||||
item.kmx_info.context[DEBUG_MAX_CONTEXT-1] = 0;
|
||||
item.kmx_info.group = group;
|
||||
_items->emplace_back(item);
|
||||
}
|
||||
|
||||
void KMX_DebugItems::fill_store_offsets(km_kbp_state_debug_kmx_info *info, PKMX_WORD index_stack) {
|
||||
|
||||
int i, n;
|
||||
|
||||
km_kbp_cp *p;
|
||||
|
||||
// TODO turn this into a struct rather than interwoven values
|
||||
for(i = n = 0, p = static_cast<LPKEY>(info->rule)->dpContext; p && *p; p = incxstr(p), i++) {
|
||||
if(*p == UC_SENTINEL && (*(p+1) == CODE_ANY || *(p+1) == CODE_NOTANY)) {
|
||||
info->store_offsets[n++] = *(p+2) - 1;
|
||||
info->store_offsets[n++] = index_stack[i];
|
||||
}
|
||||
if(*p == UC_SENTINEL && *(p+1) == CODE_INDEX) {
|
||||
info->store_offsets[n++] = *(p+2) - 1;
|
||||
info->store_offsets[n++] = index_stack[*(p+3) - 1];
|
||||
}
|
||||
if(n == DEBUG_MAX_STORE_OFFSETS*2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO split this into a separate variable
|
||||
// TODO turn this into a struct rather than interwoven values
|
||||
if(n < DEBUG_MAX_STORE_OFFSETS*2 - 1) {
|
||||
for(p = static_cast<LPKEY>(info->rule)->dpOutput; *p; p = incxstr(p)) {
|
||||
if(*p == UC_SENTINEL && *(p+1) == CODE_INDEX) {
|
||||
info->store_offsets[n++] = *(p+2) - 1;
|
||||
info->store_offsets[n++] = index_stack[*(p+3) - 1];
|
||||
}
|
||||
if(n == DEBUG_MAX_STORE_OFFSETS*2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info->store_offsets[n] = 0xFFFF;
|
||||
}
|
||||
|
|
@ -21,7 +21,18 @@ class KMX_DebugItems
|
|||
{
|
||||
private:
|
||||
debug_items *_items;
|
||||
void push_item(uint8_t type, uint32_t flags, LPGROUP group, LPKEY key, PKMX_WCHAR context);
|
||||
void push_item(
|
||||
uint8_t type,
|
||||
uint32_t flags = 0,
|
||||
LPGROUP group = nullptr,
|
||||
LPKEY key = nullptr,
|
||||
PKMX_WCHAR context = nullptr,
|
||||
PKMX_WORD index_stack = nullptr
|
||||
);
|
||||
void fill_store_offsets(
|
||||
km_kbp_state_debug_kmx_info *info,
|
||||
PKMX_WORD index_stack
|
||||
);
|
||||
public:
|
||||
KMX_DebugItems(debug_items *items);
|
||||
void push_begin(km_kbp_state_debug_key_info *key_info, uint32_t flags);
|
||||
|
|
@ -32,8 +43,18 @@ public:
|
|||
void push_nomatch_exit(LPGROUP group);
|
||||
void push_match_enter(LPGROUP group);
|
||||
void push_match_exit(LPGROUP group);
|
||||
void push_rule_enter(LPGROUP group, LPKEY key, PKMX_WCHAR context);
|
||||
void push_rule_exit(LPGROUP group, LPKEY key, PKMX_WCHAR context);
|
||||
void push_rule_enter(
|
||||
LPGROUP group,
|
||||
LPKEY key,
|
||||
PKMX_WCHAR context,
|
||||
PKMX_WORD index_stack
|
||||
);
|
||||
void push_rule_exit(
|
||||
LPGROUP group,
|
||||
LPKEY key,
|
||||
PKMX_WCHAR context,
|
||||
PKMX_WORD index_stack
|
||||
);
|
||||
};
|
||||
|
||||
inline
|
||||
|
|
@ -60,42 +81,52 @@ KMX_DebugItems::push_end(uint32_t flags) {
|
|||
|
||||
inline void
|
||||
KMX_DebugItems::push_group_enter(LPGROUP group) {
|
||||
push_item(KM_KBP_DEBUG_GROUP_ENTER, 0, group, NULL, NULL);
|
||||
push_item(KM_KBP_DEBUG_GROUP_ENTER, 0, group);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_group_exit(LPGROUP group, uint32_t flags) {
|
||||
push_item(KM_KBP_DEBUG_GROUP_EXIT, flags, group, NULL, NULL);
|
||||
push_item(KM_KBP_DEBUG_GROUP_EXIT, flags, group);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_nomatch_enter(LPGROUP group) {
|
||||
push_item(KM_KBP_DEBUG_NOMATCH_ENTER, 0, group, NULL, NULL);
|
||||
push_item(KM_KBP_DEBUG_NOMATCH_ENTER, 0, group);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_nomatch_exit(LPGROUP group) {
|
||||
push_item(KM_KBP_DEBUG_NOMATCH_EXIT, 0, group, NULL, NULL);
|
||||
push_item(KM_KBP_DEBUG_NOMATCH_EXIT, 0, group);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_match_enter(LPGROUP group) {
|
||||
push_item(KM_KBP_DEBUG_MATCH_ENTER, 0, group, NULL, NULL);
|
||||
push_item(KM_KBP_DEBUG_MATCH_ENTER, 0, group);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_match_exit(LPGROUP group) {
|
||||
push_item(KM_KBP_DEBUG_MATCH_EXIT, 0, group, NULL, NULL);
|
||||
push_item(KM_KBP_DEBUG_MATCH_EXIT, 0, group);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_rule_enter(LPGROUP group, LPKEY key, PKMX_WCHAR context) {
|
||||
push_item(KM_KBP_DEBUG_RULE_ENTER, 0, group, key, context);
|
||||
KMX_DebugItems::push_rule_enter(
|
||||
LPGROUP group,
|
||||
LPKEY key,
|
||||
PKMX_WCHAR context,
|
||||
PKMX_WORD index_stack
|
||||
) {
|
||||
push_item(KM_KBP_DEBUG_RULE_ENTER, 0, group, key, context, index_stack);
|
||||
}
|
||||
|
||||
inline void
|
||||
KMX_DebugItems::push_rule_exit(LPGROUP group, LPKEY key, PKMX_WCHAR context) {
|
||||
push_item(KM_KBP_DEBUG_RULE_EXIT, 0, group, key, context);
|
||||
KMX_DebugItems::push_rule_exit(
|
||||
LPGROUP group,
|
||||
LPKEY key,
|
||||
PKMX_WCHAR context,
|
||||
PKMX_WORD index_stack
|
||||
) {
|
||||
push_item(KM_KBP_DEBUG_RULE_EXIT, 0, group, key, context, index_stack);
|
||||
}
|
||||
|
||||
} // namespace kmx
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ KMX_BOOL KMX_Processor::ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWO
|
|||
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_end(fOutputKeystroke ? KM_KBP_DEBUG_FLAG_OUTPUTKEYSTROKE : 0);
|
||||
// m_debug_items is just a helper class that pushes to state->debug_items(),
|
||||
// so we can throw it away after we are done with it
|
||||
delete m_debug_items;
|
||||
m_debug_items = nullptr;
|
||||
}
|
||||
|
|
@ -151,6 +153,10 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
PKMX_WCHAR p;
|
||||
int sdmfI;
|
||||
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_group_enter(gp);
|
||||
}
|
||||
|
||||
/*
|
||||
If the number of nested groups goes higher than 50, then break out - this is
|
||||
a limitation of stack size. This is basically a catch-all for freaky apps that
|
||||
|
|
@ -173,6 +179,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
{
|
||||
DebugLog("Aborting output: m_state.LoopTimes exceeded.");
|
||||
m_state.StopOutput = TRUE;
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_RECURSIVE_OVERFLOW);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
@ -240,6 +249,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
PKMX_WCHAR pdeletecontext = m_context.Buf(1); // I4933
|
||||
if(!pdeletecontext || *pdeletecontext == 0) { // I4933
|
||||
m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0);
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_NOMATCH);
|
||||
}
|
||||
*pOutputKeystroke = TRUE; // I4933
|
||||
return FALSE; // I4933
|
||||
}
|
||||
|
|
@ -247,6 +259,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
} else { // I4024 // I4128 // I4287 // I4290
|
||||
DebugLog(" ... IsLegacy = FALSE; IsTIP = TRUE"); // I4128
|
||||
m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0);
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_NOMATCH);
|
||||
}
|
||||
*pOutputKeystroke = TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -254,7 +269,13 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
else if (gp->dpNoMatch != NULL && *gp->dpNoMatch != 0)
|
||||
{
|
||||
/* NoMatch rule found, and is a character key */
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_nomatch_enter(gp);
|
||||
}
|
||||
PostString(gp->dpNoMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke);
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_nomatch_exit(gp);
|
||||
}
|
||||
}
|
||||
else if (m_state.charCode != 0 && m_state.charCode != 0xFFFF && gp->fUsingKeys)
|
||||
{
|
||||
|
|
@ -262,6 +283,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
m_actions.QueueAction(QIT_CHAR, m_state.charCode);
|
||||
}
|
||||
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_NOMATCH);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +308,10 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
|
||||
m_miniContext[GLOBAL_ContextStackSize-1] = 0;
|
||||
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_rule_enter(gp, kkp, m_miniContext, m_indexStack);
|
||||
}
|
||||
|
||||
/*
|
||||
The next section includes several optimizations that make the code a little harder
|
||||
to read, but are probably worth it in the time that they save.
|
||||
|
|
@ -319,12 +347,24 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
|
||||
/* Use PostString to post the rest of the output string. */
|
||||
|
||||
if(PostString(p, m_keyboard.Keyboard, NULL, pOutputKeystroke) == psrCheckMatches)
|
||||
{
|
||||
if(gp->dpMatch && *gp->dpMatch)
|
||||
{
|
||||
PostString(gp->dpMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke);
|
||||
KMX_BOOL shouldProcessNomatch = PostString(p, m_keyboard.Keyboard, NULL, pOutputKeystroke) == psrCheckMatches;
|
||||
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_rule_exit(gp, kkp, m_miniContext, m_indexStack);
|
||||
}
|
||||
|
||||
if(shouldProcessNomatch && gp->dpMatch && *gp->dpMatch) {
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_match_enter(gp);
|
||||
}
|
||||
PostString(gp->dpMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke);
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_match_exit(gp);
|
||||
}
|
||||
}
|
||||
|
||||
if(m_debug_items) {
|
||||
m_debug_items->push_group_exit(gp, 0);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
|
|
|||
|
|
@ -7,43 +7,126 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <keyman/keyboardprocessor.h>
|
||||
#include "path.hpp"
|
||||
#include "state.hpp"
|
||||
#include "../test_assert.h"
|
||||
#include "kmx/kmx_base.h"
|
||||
#include "kmx/kmx_xstring.h"
|
||||
|
||||
using namespace km::kbp::kmx;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// https://stackoverflow.com/a/29865/1836776
|
||||
void hexdump(void const *ptr, int buflen) {
|
||||
unsigned char *buf = (unsigned char*)ptr;
|
||||
int i, j;
|
||||
for (i=0; i<buflen; i+=16) {
|
||||
printf("%06x: ", i);
|
||||
for (j=0; j<16; j++)
|
||||
if (i+j < buflen)
|
||||
printf("%02x ", buf[i+j]);
|
||||
else
|
||||
printf(" ");
|
||||
printf(" ");
|
||||
for (j=0; j<16; j++)
|
||||
if (i+j < buflen)
|
||||
printf("%c", isprint(buf[i+j]) ? buf[i+j] : '.');
|
||||
printf("\n");
|
||||
}
|
||||
const char *debug_item_types[] = {
|
||||
"KM_KBP_DEBUG_BEGIN", // = 0,
|
||||
"//KM_KBP_DEBUG_BEGIN_ANSI", // = 1, // not supported; instead rewrite ansi keyboards to Unicode with mcompile
|
||||
"KM_KBP_DEBUG_GROUP_ENTER", // = 2,
|
||||
"KM_KBP_DEBUG_GROUP_EXIT", // = 3,
|
||||
"KM_KBP_DEBUG_RULE_ENTER", // = 4,
|
||||
"KM_KBP_DEBUG_RULE_EXIT", // = 5,
|
||||
"KM_KBP_DEBUG_MATCH_ENTER", // = 6,
|
||||
"KM_KBP_DEBUG_MATCH_EXIT", // = 7,
|
||||
"KM_KBP_DEBUG_NOMATCH_ENTER", // = 8,
|
||||
"KM_KBP_DEBUG_NOMATCH_EXIT", // = 9,
|
||||
"KM_KBP_DEBUG_END" // = 10,
|
||||
};
|
||||
|
||||
void print_debug_item(const char *title, km_kbp_state_debug_item const & item) {
|
||||
LPGROUP gp = static_cast<LPGROUP>(item.kmx_info.group);
|
||||
LPKEY rule = static_cast<LPKEY>(item.kmx_info.rule);
|
||||
|
||||
std::cout
|
||||
<< "debug_item " << title << std::endl
|
||||
<< " type: " << debug_item_types[item.type] << std::endl
|
||||
<< " flags: " << item.flags << std::endl
|
||||
<< " key_info: (vk: " << item.key_info.vk
|
||||
<< " mod: " << item.key_info.modifier_state
|
||||
<< " char: " << item.key_info.character << ")" << std::endl;
|
||||
|
||||
std::cout
|
||||
<< " kmx_info: "<< std::endl
|
||||
<< " context: '" << item.kmx_info.context << "'" << std::endl;
|
||||
|
||||
if(gp) std::cout
|
||||
<< " group: " << gp->dpName << std::endl;
|
||||
if(rule) std::cout
|
||||
<< " rule: " << std::endl
|
||||
<< " key: " << rule->Key << std::endl
|
||||
<< " shift: " << rule->ShiftFlags << std::endl
|
||||
<< " line: " << rule->Line << std::endl;
|
||||
std::cout
|
||||
<< " offsets: ";
|
||||
std::copy(
|
||||
std::begin(item.kmx_info.store_offsets),
|
||||
std::end(item.kmx_info.store_offsets),
|
||||
std::ostream_iterator<uint16_t>(std::cout, " ")
|
||||
);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
bool are_store_offsets_equal(const uint16_t (&lhs)[DEBUG_STORE_OFFSETS_SIZE], const uint16_t (&rhs)[DEBUG_STORE_OFFSETS_SIZE]) {
|
||||
return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
|
||||
// !memcmp(lhs, rhs, sizeof(lhs));
|
||||
}
|
||||
|
||||
bool operator==(km_kbp_state_debug_item const & lhs,
|
||||
km_kbp_state_debug_item const & rhs)
|
||||
{
|
||||
int result = memcmp(&lhs, &rhs, sizeof(km_kbp_state_debug_item));
|
||||
if(result != 0) {
|
||||
hexdump(&lhs, sizeof(km_kbp_state_debug_item));
|
||||
hexdump(&rhs, sizeof(km_kbp_state_debug_item));
|
||||
auto result = (lhs.type == rhs.type && lhs.flags == rhs.flags);
|
||||
if(result) {
|
||||
LPGROUP lgp = static_cast<LPGROUP>(lhs.kmx_info.group), rgp = static_cast<LPGROUP>(rhs.kmx_info.group);
|
||||
LPKEY lrule = static_cast<LPKEY>(lhs.kmx_info.rule), rrule = static_cast<LPKEY>(rhs.kmx_info.rule);
|
||||
switch(lhs.type) {
|
||||
case KM_KBP_DEBUG_BEGIN:
|
||||
result = lhs.key_info.character == rhs.key_info.character &&
|
||||
lhs.key_info.modifier_state == rhs.key_info.modifier_state &&
|
||||
lhs.key_info.vk == rhs.key_info.vk;
|
||||
break;
|
||||
case KM_KBP_DEBUG_END:
|
||||
break;
|
||||
case KM_KBP_DEBUG_GROUP_ENTER:
|
||||
case KM_KBP_DEBUG_GROUP_EXIT:
|
||||
case KM_KBP_DEBUG_MATCH_ENTER:
|
||||
case KM_KBP_DEBUG_MATCH_EXIT:
|
||||
case KM_KBP_DEBUG_NOMATCH_ENTER:
|
||||
case KM_KBP_DEBUG_NOMATCH_EXIT:
|
||||
assert(lgp != nullptr);
|
||||
assert(rgp != nullptr);
|
||||
assert(lgp->dpName != nullptr);
|
||||
assert(rgp->dpName != nullptr);
|
||||
result = !u16cmp(lgp->dpName, rgp->dpName);
|
||||
break;
|
||||
case KM_KBP_DEBUG_RULE_ENTER:
|
||||
case KM_KBP_DEBUG_RULE_EXIT:
|
||||
assert(lgp != nullptr);
|
||||
assert(rgp != nullptr);
|
||||
assert(lgp->dpName != nullptr);
|
||||
assert(rgp->dpName != nullptr);
|
||||
assert(lrule != nullptr);
|
||||
assert(rrule != nullptr);
|
||||
result = !u16cmp(lgp->dpName, rgp->dpName) &&
|
||||
lrule->Line == rrule->Line &&
|
||||
lrule->Key == rrule->Key &&
|
||||
lrule->ShiftFlags == rrule->ShiftFlags &&
|
||||
u16cmp(lhs.kmx_info.context, rhs.kmx_info.context) == 0 &&
|
||||
are_store_offsets_equal(lhs.kmx_info.store_offsets, rhs.kmx_info.store_offsets);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result == 0;
|
||||
|
||||
if(!result) {
|
||||
print_debug_item("actual", lhs);
|
||||
print_debug_item("expected", rhs);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -53,8 +136,18 @@ bool debug_items(km_kbp_state const * state,
|
|||
size_t n = 0;
|
||||
auto act = km_kbp_state_debug_items(state, &n);
|
||||
|
||||
for (auto &rhs: expected)
|
||||
for (auto &rhs: expected) {
|
||||
if(--n < 0) {
|
||||
std::cout << "expected longer than actual" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!(*act++ == rhs)) return false;
|
||||
}
|
||||
|
||||
if(n != 0) {
|
||||
std::cout << "actual longer than expected" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -67,15 +160,73 @@ bool debug_items(km_kbp_state const * state,
|
|||
} // namespace
|
||||
|
||||
|
||||
// TODO: consider using same const approach in kmx_base.h
|
||||
// but this ripples through a whole lot of code
|
||||
typedef struct tagDEBUG_GROUP
|
||||
{
|
||||
KMX_WCHAR const * dpName;
|
||||
KEY const * dpKeyArray; // [LPKEY] address of first item in key array
|
||||
KMX_WCHAR const * dpMatch;
|
||||
KMX_WCHAR const * dpNoMatch;
|
||||
KMX_DWORD cxKeyArray; // in array entries
|
||||
KMX_BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not
|
||||
} DEBUG_GROUP, *LPDEBUG_GROUP;
|
||||
|
||||
typedef struct tagDEBUG_KEY
|
||||
{
|
||||
KMX_WCHAR Key;
|
||||
KMX_DWORD Line;
|
||||
KMX_DWORD ShiftFlags;
|
||||
KMX_WCHAR const * dpOutput;
|
||||
KMX_WCHAR const * dpContext;
|
||||
} DEBUG_KEY, *LPDEBUG_KEY;
|
||||
|
||||
|
||||
km_kbp_keyboard * test_kb = nullptr;
|
||||
km_kbp_state * test_state = nullptr;
|
||||
km_kbp_context_item * citems = nullptr;
|
||||
char * arg_path;
|
||||
|
||||
void teardown() {
|
||||
if(citems) {
|
||||
km_kbp_context_items_dispose(citems);
|
||||
citems = nullptr;
|
||||
}
|
||||
if(test_state) {
|
||||
km_kbp_state_dispose(test_state);
|
||||
test_state = nullptr;
|
||||
}
|
||||
if(test_kb) {
|
||||
km_kbp_keyboard_dispose(test_kb);
|
||||
test_kb = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void setup(const char *keyboard) {
|
||||
teardown();
|
||||
|
||||
km::kbp::path path = km::kbp::path::join(arg_path, "..", "kmx", keyboard);
|
||||
|
||||
try_status(km_kbp_keyboard_load(path.native().c_str(), &test_kb));
|
||||
try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state));
|
||||
try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems));
|
||||
|
||||
// Pre-test sanity: ensure debugging is disabled
|
||||
assert(km_kbp_state_debug_get(test_state) == 0);
|
||||
|
||||
// Ensure the pre-run debug item state is not empty
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END}
|
||||
}));
|
||||
|
||||
void setup() {
|
||||
try_status(km_kbp_context_set(km_kbp_state_context(test_state), citems));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 1: Start with debugging disabled
|
||||
*/
|
||||
void test_debugging_disabled() {
|
||||
setup("000 - null keyboard.kmx");
|
||||
try_status(km_kbp_state_debug_set(test_state, 0));
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT));
|
||||
assert(debug_items(test_state, {
|
||||
|
|
@ -83,24 +234,176 @@ void test_debugging_disabled() {
|
|||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2: Debugging enabled, no rule match
|
||||
*/
|
||||
void test_debugging_no_rule_match() {
|
||||
setup("000 - null keyboard.kmx");
|
||||
DEBUG_GROUP gp = {u"Main"};
|
||||
try_status(km_kbp_state_debug_set(test_state, 1));
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {{KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 'S'}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 'S'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 3: Debugging enabled, function key pressed, no rule match
|
||||
*/
|
||||
void test_debugging_function_key() {
|
||||
setup("000 - null keyboard.kmx");
|
||||
DEBUG_GROUP gp = {u"Main"};
|
||||
try_status(km_kbp_state_debug_set(test_state, 1));
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F1, 0));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {{KM_KBP_VKEY_F1, 0, 0}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_F1, 0, 0}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, KM_KBP_DEBUG_FLAG_OUTPUTKEYSTROKE}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 4: basic rule match
|
||||
*/
|
||||
void test_basic_rule_matches() {
|
||||
setup("002 - basic input Unicode.kmx");
|
||||
DEBUG_GROUP gp = {u"Main"};
|
||||
DEBUG_KEY kp = { 'F', /*line*/16, /*shift*/0 }; // vkey is a char
|
||||
try_status(km_kbp_state_debug_set(test_state, 1));
|
||||
|
||||
// 'DE' + 'F' > U+0E04 U+0E05 U+0E06
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT, 'D'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a default 'D'
|
||||
}));
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT, 'E'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a default 'E'
|
||||
}));
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT, 'F'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"DE", &gp, &kp, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"DE", &gp, &kp, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit rule transform
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 5: Multiple groups
|
||||
*/
|
||||
void test_multiple_groups() {
|
||||
setup("030 - multiple groups.kmx");
|
||||
DEBUG_GROUP gp = {u"Main"}, gpa = {u"a"}, gpb = {u"b"};
|
||||
DEBUG_KEY kp1 = { KM_KBP_VKEY_1, /*line*/12, /*shift*/KM_KBP_MODIFIER_VIRTUALKEY },
|
||||
kp2 = { KM_KBP_VKEY_2, /*line*/13, /*shift*/KM_KBP_MODIFIER_VIRTUALKEY },
|
||||
kpa = { 0, /*line*/19, /*shift*/0 },
|
||||
kpb = { 0, /*line*/23, /*shift*/0 };
|
||||
|
||||
try_status(km_kbp_state_debug_set(test_state, 1));
|
||||
|
||||
// '12' -> 'abc'
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_1, 0));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_1, 0, '1'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"", &gp, &kp1, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"", &gp, &kp1, {0xFFFF}}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_ENTER, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gpa}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"a", &gpa, &kpa, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"a", &gpa, &kpa, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gpa}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_EXIT, 0, {}, {u"", &gp}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a 'b'
|
||||
}));
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_2, 0));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_2, 0, '2'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"b", &gp, &kp2, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gpb}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"b", &gpb, &kpb, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"b", &gpb, &kpb, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gpb}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"b", &gp, &kp2, {0xFFFF}}},
|
||||
|
||||
//See #5440 for why match does not fire here:
|
||||
//km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_ENTER, 0, {}, {u"", &gp}},
|
||||
// km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gpa}},
|
||||
// km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gpa}},
|
||||
//km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_EXIT, 0, {}, {u"", &gp}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will "abc"
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 6: store offsets
|
||||
*/
|
||||
void test_store_offsets() {
|
||||
setup("044 - if and context.kmx");
|
||||
DEBUG_GROUP gp = {u"Main"};
|
||||
DEBUG_KEY kpa = { 'a', /*line*/15, },
|
||||
kpb = { 'b', /*line*/19, };
|
||||
|
||||
try_status(km_kbp_state_debug_set(test_state, 1));
|
||||
|
||||
// 'ab' -> 'ex'
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_A, 0));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_A, 0, 'a'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"", &gp, &kpa, {0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"", &gp, &kpa, {0xFFFF}}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a 'exay'
|
||||
}));
|
||||
|
||||
try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_B, 0));
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_B, 0, 'b'}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
|
||||
|
||||
// store_offsets: 6 = store #, 1 = index into store, 6 = store #, 0 = index into store
|
||||
// store: store(diaeresisBase) 'ae'
|
||||
// context: exay
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"exay", &gp, &kpb, {6, 1, 6, 0, 0xFFFF}}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"exay", &gp, &kpb, {6, 1, 6, 0, 0xFFFF}}},
|
||||
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}},
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0},
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
constexpr const auto help_str = "\
|
||||
debug_api [--color] <SOURCE_PATH>\n\
|
||||
\n\
|
||||
|
|
@ -129,38 +432,18 @@ int main(int argc, char *argv []) {
|
|||
}
|
||||
console_color::enabled = console_color::isaterminal() || arg_color;
|
||||
|
||||
auto arg_path = argv[arg_color ? 2 : 1];
|
||||
arg_path = argv[arg_color ? 2 : 1];
|
||||
|
||||
km::kbp::path path = km::kbp::path::join(arg_path, "..", "kmx", "000 - null keyboard.kmx");
|
||||
|
||||
try_status(km_kbp_keyboard_load(path.native().c_str(), &test_kb));
|
||||
try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state));
|
||||
try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems));
|
||||
|
||||
// Pre-test sanity: ensure debugging is disabled
|
||||
assert(km_kbp_state_debug_get(test_state) == 0);
|
||||
|
||||
// Ensure the pre-run debug item state is not empty
|
||||
assert(debug_items(test_state, {
|
||||
km_kbp_state_debug_item{KM_KBP_DEBUG_END}
|
||||
}));
|
||||
|
||||
// Test 1: Start with debugging disabled
|
||||
setup();
|
||||
test_debugging_disabled();
|
||||
|
||||
// Test 2: Debugging enabled, no rule match
|
||||
setup();
|
||||
test_debugging_no_rule_match();
|
||||
|
||||
// Test 3: Debugging enabled, function key pressed, no rule match
|
||||
setup();
|
||||
test_debugging_function_key();
|
||||
test_basic_rule_matches();
|
||||
test_multiple_groups();
|
||||
test_store_offsets();
|
||||
|
||||
// Destroy them
|
||||
km_kbp_context_items_dispose(citems);
|
||||
km_kbp_state_dispose(test_state);
|
||||
km_kbp_keyboard_dispose(test_kb);
|
||||
teardown();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue