test(linux): Add unit tests for mcompile#15892
Conversation
User Test ResultsTest specification and instructions User tests are not required |
This is temporary, when the transition to xkblibcommon is done, wayland will be available.
ermshiperete
left a comment
There was a problem hiding this comment.
:+1 Looks good.
As we talked in the huddle, I'm not yet approving it so that it doesn't get accidentally merged while the tests won't work on the noble ba.
|
We have a convention for pull request titles Referencehttps://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes#title I suggest changing the title to something like This way, the |
- adding header files - renaming files and methods - restructuring meson build
Co-authored-by: Eberhard Beilharz <ermshiperete@users.noreply.github.com>
…-keyboard-support-unit-tests' into feat/linux/mnemonic-keyboard-support-unit-tests
| EXPECT_EQ(keycodes.size(), expected_keysyms.size()) << "Keycodes and expected keysyms vectors must be of the same size."; | ||
| for (guint k = 0; k < keycodes.size() && k < expected_keysyms.size(); k++) { |
There was a problem hiding this comment.
I think it doesn't make sense to continue if the the two vectors have different size, so we could use ASSERT_EQ here. That would also simplify the for loop slightly. Also, since we're using a std vector it doesn't really make sense to use a Gnome type for k - doesn't really matter but will reduce dependency. (I'm not sure - is there a uint or should it be unsigned int).
| EXPECT_EQ(keycodes.size(), expected_keysyms.size()) << "Keycodes and expected keysyms vectors must be of the same size."; | |
| for (guint k = 0; k < keycodes.size() && k < expected_keysyms.size(); k++) { | |
| ASSERT_EQ(keycodes.size(), expected_keysyms.size()) << "Keycodes and expected keysyms vectors must be of the same size."; | |
| for (uint k = 0; k < keycodes.size(); k++) { |
There was a problem hiding this comment.
When ASSERT_* fails, doesn't it prevent all further instantiations from running?
There was a problem hiding this comment.
Maybe, but my understanding/assumption is that it won't execute the following checks but continue with the next instantiation.
- formatting - improved constructors
| AltGrDe, | ||
| GetKeyValUnderlyingFromKeyCodeUnderlyingTest, | ||
| testing::ValuesIn(KeyboardTestParameters( | ||
| {u'æ', u'\xfffe', u'¢', u'ð', u'\xfffe', u'\xfffe', u'\xfffe', u'\xfffe', u'\xfffe', u'\xffff', |
There was a problem hiding this comment.
Why is it that we have u'\xffff' for some values and u'\xfffe' or u'\000' for others?
ermshiperete
left a comment
There was a problem hiding this comment.
Oops, forgot to check devin.ai! That found a few issues that might cause problems in the future.
Co-authored-by: Eberhard Beilharz <ermshiperete@users.noreply.github.com>
Co-authored-by: Eberhard Beilharz <ermshiperete@users.noreply.github.com>
Co-authored-by: Eberhard Beilharz <ermshiperete@users.noreply.github.com>
Co-authored-by: Eberhard Beilharz <ermshiperete@users.noreply.github.com>
| if (test_display) { | ||
| gdk_display_close(test_display); | ||
| test_display = nullptr; | ||
| } |
There was a problem hiding this comment.
From devin.ai:
gdk_display_close in TearDown breaks subsequent parameterized test instances
The KeyboardConversionTest fixture calls gdk_display_close(test_display) in TearDown() (keymap.tests.cpp:140). This fixture is used with testing::WithParamInterface, meaning GTest creates a new fixture instance per parameter value, calling SetUp/TearDown for each. However, gdk_init() in SetUp (keymap.tests.cpp:89) is a one-time initialization — subsequent calls are no-ops and will not reopen the display. After the first test's TearDown closes the display, all subsequent test instances will call gdk_display_get_default() and get a closed display. The GDK documentation explicitly states gdk_display_close "is not a reversible operation" and "should not be called normally." This could cause the ASSERT_NE(test_display, nullptr) or ASSERT_NE(test_keymap, nullptr) checks to fail for every test after the first, or cause undefined behavior when using the keymap from a closed display.
| include_directories: [common_include_dir], | ||
| include_directories: [common_include_dir], |
There was a problem hiding this comment.
| include_directories: [common_include_dir], | |
| include_directories: [common_include_dir], | |
| include_directories: [common_include_dir], |
| if (KMX_DoConvert(kmxfile, bDeadkeyConversion, argc, (gchar**)argv)) { | ||
| if(!KMX_SaveKeyboard(kmxfile, outfile)) { | ||
| KMX_LogError(L"Failed to save keyboard (%d)\n", errno); | ||
| delete[] kmxfile; | ||
| return 3; | ||
| } | ||
| } | ||
|
|
||
| delete[] kmxfile; | ||
|
|
||
| return 0; |
There was a problem hiding this comment.
From devin.ai:
Program returns success (0) when KMX_DoConvert fails — pre-existing issue
In main.cpp:78-86, when KMX_DoConvert returns FALSE, the code falls through to delete[] kmxfile; return 0;, returning success even though conversion failed. This is the same behavior as the old code in mcompile.cpp and is consistent with the comment at mcompile.cpp:8-9 that the program 'deliberately leaks memory as it has a very short life cycle.' However, a non-zero exit code would be more appropriate for a failed conversion.
| * Mnemonic layout support for Linux | ||
| */ | ||
|
|
||
| #include "mcompile.h" |
There was a problem hiding this comment.
From devin.ai:
main.cpp relies on transitive includes for strcmp, printf, and errno
main.cpp uses strcmp (line 24), printf (lines 29, 44), and errno (lines 73, 80) but only directly includes mcompile.h. These symbols are available through transitive includes: mcompile.h → <gdk/gdk.h> → <glib.h> → various C standard headers. This works but is fragile — a change in GDK's internal includes could break compilation. The old code had the same issue. Consider adding explicit #include <cstring>, #include <cstdio>, and #include <cerrno>.
| } | ||
| } | ||
|
|
||
| delete[] kmxfile; |
There was a problem hiding this comment.
From devin.ai - took me a while to understand this, but I think the analysis is correct. And I guess the other two delete[] lines will have to be changed as well:
delete[] on LPKMX_KEYBOARD is technically UB but an improvement over the old delete
In main.cpp:74,81,86, delete[] kmxfile is used where kmxfile is LPKMX_KEYBOARD (i.e., KMX_KEYBOARD*), but the underlying memory was allocated as new KMX_BYTE[size] in KMX_LoadKeyboard (mc_kmxfile.cpp:439-441). Using delete[] with a different type than new[] is technically undefined behavior per the C++ standard. However, both KMX_BYTE and KMX_KEYBOARD have trivial destructors, so in practice this is benign. The old code used delete kmxfile (scalar delete on array allocation), which was even more wrong. The PR improves this, though the correct fix would be to cast back to PKMX_BYTE before deleting: delete[] reinterpret_cast<PKMX_BYTE>(kmxfile);.
| delete[] kmxfile; | |
| delete[] reinterpret_cast<PKMX_BYTE>(kmxfile); |
| } | ||
| } | ||
|
|
||
| delete[] kmxfile; |
There was a problem hiding this comment.
We could consider a further refactoring (in a separate PR):
add a field (buf) to the end of KMX_KEYBOARD that holds the original data. That would be cleaner. Then we could do delete kmxfile. The d'tor of KMX_KEYBOARD could then delete[] buf.
Also, the variable name kmxfile should probably be kmxkeyboard.
| KMX_WCHAR expected_char; | ||
| std::string layout; | ||
| guint shiftstate; | ||
| KMX_WCHAR expected_deadkey_value; |
There was a problem hiding this comment.
We don't need/use this variable, and KeyboardTestDataValues (that uses it) has it's own variable with the same name.
| KMX_WCHAR expected_deadkey_value; |
@keymanapp-test-bot skip
This is a first try to add unit tests to mcompile.
There is a new file
converter.cppcontaining a minimal main. This allows the tests to have an own main provided by googletest.Since gsettings are used, there is the need for new include files and new library dependencies.
The separation of converter.cpp required some refactoring of the header files.