Open ilikeheaps opened 1 week ago
I tried making a simple test for it (dirty copying other tests) but can’t get hands swap setup to work in the test yet. Pasting patch here rather than draft PR because I’m having some issues with ssh setup (and GitHub somehow doesn't accept patch files as attachments).
diff --git a/tests/swap_hands/config.h b/tests/swap_hands/config.h
new file mode 100644
index 0000000000..76578428a7
--- /dev/null
+++ b/tests/swap_hands/config.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "test_common.h"
+
+#define ACTION_DEBUG
diff --git a/tests/swap_hands/test.cpp b/tests/swap_hands/test.cpp
new file mode 100644
index 0000000000..991a9d215a
--- /dev/null
+++ b/tests/swap_hands/test.cpp
@@ -0,0 +1,41 @@
+#include "action_util.h"
+#include "keyboard_report_util.hpp"
+#include "test_common.hpp"
+
+using testing::_;
+using testing::InSequence;
+
+class SH : public TestFixture {};
+class SHParametrizedTestFixture : public ::testing::WithParamInterface<std::pair<KeymapKey, KeymapKey>>, public SH {};
+
+const keypos_t hand_swap_config[MATRIX_ROWS][MATRIX_COLS] = {
+ {{0, 0}, {0, 2}, {0, 1}}
+};
+
+TEST_F(SH, Dummy) {
+ TestDriver driver;
+ auto key_sh_t = KeymapKey(0, 0, 0, SH_T(KC_SPACE));
+ auto key_a = KeymapKey(0, 0, 1, KC_A);
+ auto key_b = KeymapKey(0, 0, 2, KC_B);
+
+ set_keymap({key_sh_t, key_a, key_b});
+
+ debug_enable = true;
+ debug_keyboard = true;
+
+ key_sh_t.press();
+ idle_for(722);
+ assert(is_swap_hands_on());
+ printf("pre\n");
+ key_a.press();
+ idle_for(3);
+ printf("post\n");
+ keyboard_task();
+ EXPECT_REPORT(driver, (key_b.report_code)); // somehow SH_T registers a tap instead
+ key_sh_t.release();
+ keyboard_task();
+ key_a.release();
+
+ EXPECT_EMPTY_REPORT(driver);
+ keyboard_task();
+}
diff --git a/tests/swap_hands/test.mk b/tests/swap_hands/test.mk
new file mode 100644
index 0000000000..f3f730c6ad
--- /dev/null
+++ b/tests/swap_hands/test.mk
@@ -0,0 +1,2 @@
+SWAP_HANDS_ENABLE = yes
+CONSOLE_ENABLE = yes
Oh, found the problem. Mixed col/row order in two places so it avoided an obvious error and unexpectedly mapped A to SH_T. So basic case works now and I will try to reproduce my issue.
Alright, this seems to reproduce my issue:
diff --git a/tests/swap_hands/config.h b/tests/swap_hands/config.h
new file mode 100644
index 0000000000..76578428a7
--- /dev/null
+++ b/tests/swap_hands/config.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "test_common.h"
+
+#define ACTION_DEBUG
diff --git a/tests/swap_hands/test.cpp b/tests/swap_hands/test.cpp
new file mode 100644
index 0000000000..92d0c86920
--- /dev/null
+++ b/tests/swap_hands/test.cpp
@@ -0,0 +1,66 @@
+#include "action_util.h"
+#include "keyboard_report_util.hpp"
+#include "test_common.hpp"
+
+using testing::_;
+using testing::InSequence;
+
+class SH : public TestFixture {};
+class SHParametrizedTestFixture : public ::testing::WithParamInterface<std::pair<KeymapKey, KeymapKey>>, public SH {};
+
+// XXX hand_swap_config[row][column] -> column, row
+const keypos_t hand_swap_config[MATRIX_ROWS][MATRIX_COLS] = {
+ {{0, 0}, {2, 0}, {1, 0}}
+};
+
+// works
+TEST_F(SH, SwapHandsTapLongHoldSwaps) {
+ TestDriver driver;
+ auto key_sh_t = KeymapKey(0, 0, 0, SH_T(KC_SPACE));
+ auto key_a = KeymapKey(0, 1, 0, KC_A);
+ auto key_b = KeymapKey(0, 2, 0, KC_B);
+
+ set_keymap({key_sh_t, key_a, key_b});
+
+ debug_enable = true;
+ debug_keyboard = true;
+
+ key_sh_t.press();
+ idle_for(211);
+ assert(is_swap_hands_on());
+ key_a.press();
+ EXPECT_REPORT(driver, (key_b.report_code));
+ idle_for(1);
+ key_sh_t.release();
+ idle_for(1);
+ key_a.release();
+
+ EXPECT_EMPTY_REPORT(driver);
+ keyboard_task();
+}
+
+// fails
+TEST_F(SH, SwapHandsTapShortHoldSwaps) {
+ TestDriver driver;
+ auto key_sh_t = KeymapKey(0, 0, 0, SH_T(KC_SPACE));
+ auto key_a = KeymapKey(0, 1, 0, KC_A);
+ auto key_b = KeymapKey(0, 2, 0, KC_B);
+
+ set_keymap({key_sh_t, key_a, key_b});
+
+ debug_enable = true;
+ debug_keyboard = true;
+
+ key_sh_t.press();
+ idle_for(1);
+ assert(is_swap_hands_on());
+ key_a.press();
+ EXPECT_REPORT(driver, (key_b.report_code));
+ idle_for(1);
+ key_sh_t.release();
+ idle_for(1);
+ key_a.release();
+
+ EXPECT_EMPTY_REPORT(driver);
+ keyboard_task();
+}
diff --git a/tests/swap_hands/test.mk b/tests/swap_hands/test.mk
new file mode 100644
index 0000000000..f3f730c6ad
--- /dev/null
+++ b/tests/swap_hands/test.mk
@@ -0,0 +1,2 @@
+SWAP_HANDS_ENABLE = yes
+CONSOLE_ENABLE = yes
Oh right, I could paste test output here (with some mysterious debug prints):
[nix-shell:~/mnt/by-label/tmp-transfer-bkp/messy/qmk_firmware]$ make test:swap_hands
QMK Firmware 0.26.4
Making test swap_hands
gcc (GCC) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Compiling: tests/swap_hands/test.cpp [OK]
Linking: .build/test/swap_hands.elf [OK]
Testing swap_hands
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from SH
default_layer_state: 00000001(0) to 00000001(0)
[ RUN ] SH.SwapHandsTapLongHoldSwaps
---- action_exec: start -----
EVENT: 0000d(0)
hello
yes?
0 0 1 0 sh 0
Tapping: Start(Press tap key).
toggle
TAPPING_KEY=0000d(0):0
processed: 0000d(0):0
Tapping: End. Timeout. Not tap(0): 0000u(200)
ACTION: ACT_SWAP_HANDS[0:2C] layer_state: 00000000(0) default_layer_state: 00000001(0)
TAPPING_KEY=0000u(0):0
---- action_exec: start -----
EVENT: 0001d(211)
hello
yes?
1 0 1 1 sh 1
yes
ACTION: ACT_LMODS[0:05] layer_state: 00000000(0) default_layer_state: 00000001(0)
keyboard_report: 00 | 05 00 00 00 00 00
processed: 0002d(211):0
---- action_exec: start -----
EVENT: 0000u(212)
hello
yes?
0 0 0 0 sh 1
ACTION: ACT_SWAP_HANDS[0:2C] layer_state: 00000000(0) default_layer_state: 00000001(0)
toggle
processed: 0000u(212):0
---- action_exec: start -----
EVENT: 0001u(213)
hello
yes?
1 0 0 1 sh 0
yes
ACTION: ACT_LMODS[0:05] layer_state: 00000000(0) default_layer_state: 00000001(0)
keyboard_report: 00 | 00 00 00 00 00 00
processed: 0002u(213):0
layer_state: 00000000(0) to 00000000(0)
[ OK ] SH.SwapHandsTapLongHoldSwaps (0 ms)
[ RUN ] SH.SwapHandsTapShortHoldSwaps
---- action_exec: start -----
EVENT: 0000d(0)
hello
yes?
0 0 1 0 sh 0
Tapping: Start(Press tap key).
toggle
TAPPING_KEY=0000d(0):0
processed: 0000d(0):0
---- action_exec: start -----
EVENT: 0001d(1)
hello
yes?
1 0 1 1 sh 1
yes
waiting_buffer_enq: { [0]=0002d(1):0 }
---- action_exec: process waiting_buffer -----
---- action_exec: start -----
EVENT: 0000u(2)
hello
yes?
0 0 0 0 sh 1
Tapping: First tap(0->1).
TAPPING_KEY=0000d(0):1-
ACTION: ACT_SWAP_HANDS[0:2C] layer_state: 00000000(0) default_layer_state: 00000001(0)
toggle
unknown file: Failure
Unexpected mock function call - returning directly.
Function call: send_keyboard_mock(@0x46d0d8 report: (KC_SPACE) []
)
Google Mock tried the following 1 expectation, but it didn't match:
tests/swap_hands/test.cpp:58: EXPECT_CALL((driver), send_keyboard_mock(KeyboardReport (key_b.report_code)))...
Expected arg #0: is equal to report: (KC_B) []
Actual: report: (KC_SPACE) []
Expected: to be called once
Actual: never called - unsatisfied and active
keyboard_report: 00 | 2C 00 00 00 00 00
waiting_buffer_enq: { [0]=0002d(1):0 [1]=0000u(2):1- }
---- action_exec: process waiting_buffer -----
Tapping: key event while last tap(>0).
ACTION: ACT_LMODS[0:05] layer_state: 00000000(0) default_layer_state: 00000001(0)
unknown file: Failure
Unexpected mock function call - returning directly.
Function call: send_keyboard_mock(@0x46d0d8 report: (KC_B, KC_SPACE) []
)
Google Mock tried the following 1 expectation, but it didn't match:
tests/swap_hands/test.cpp:58: EXPECT_CALL((driver), send_keyboard_mock(KeyboardReport (key_b.report_code)))...
Expected arg #0: is equal to report: (KC_B) []
Actual: report: (KC_B, KC_SPACE) []
Expected: to be called once
Actual: never called - unsatisfied and active
keyboard_report: 00 | 2C 05 00 00 00 00
processed: waiting_buffer[0] =0002d(1):0
Tapping: Tap release(1)
ACTION: ACT_SWAP_HANDS[0:2C] layer_state: 00000000(0) default_layer_state: 00000001(0)
keyboard_report: 00 | 00 05 00 00 00 00
TAPPING_KEY=0000u(0):0
processed: waiting_buffer[1] =0000u(0):0
---- action_exec: start -----
EVENT: 0001u(3)
hello
yes?
1 0 0 1 sh 0
yes
ACTION: ACT_LMODS[0:05] layer_state: 00000000(0) default_layer_state: 00000001(0)
keyboard_report: 00 | 00 00 00 00 00 00
processed: 0002u(3):0
layer_state: 00000000(0) to 00000000(0)
SH.SwapHandsTapShortHoldSwaps failed!
[ LEVEL ] [TIME] [EVENT]
[ INFO ] 0 tapping term is 200ms
[ TRACE ] 0 pressed: unknown keycode: 0x562c. Add conversion to generate_identifier
[ TRACE ] 0 1 keyboard task loop
[ TRACE ] 1 pressed: KC_A
[ TRACE ] 1 1 keyboard task loop
[ TRACE ] 2 released: unknown keycode: 0x562c. Add conversion to generate_identifier was pressed for 2ms
[ TRACE ] 2 1 keyboard task loop
[ TRACE ] 2 report: (KC_SPACE) []
[ TRACE ] 2 report: (KC_B, KC_SPACE) []
[ TRACE ] 2 report: (KC_B) []
[ TRACE ] 3 released: KC_A was pressed for 2ms
[ TRACE ] 3 report: empty
[ INFO ] 3 test fixture clean-up start.
[ TRACE ] 3 2000 keyboard task loops
[ TRACE ] 2003 2000 keyboard task loops
[ INFO ] 4003 test fixture clean-up end.
[ FAILED ] SH.SwapHandsTapShortHoldSwaps (0 ms)
[----------] 2 tests from SH (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] SH.SwapHandsTapShortHoldSwaps
1 FAILED TEST
Make finished with errors
make: *** [Makefile:417: test:swap_hands] Error 1
Edit: mixed up revisions
Okay, found the (edit: an?) issue: I should read the manual and use PERMISSIVE_HOLD. Well, I did read it but didn't notice different tapping modes when reading about swap-hands and one shot modifiers. I assumed PERMISSIVE_HOLD behaviour when reading keys descriptions:
The Mod-Tap key MT(mod, kc) acts like a modifier when held, and a regular keycode when tapped. In other words, you can have a key that sends Escape when you tap it, but functions as a Control or Shift key when you hold it down.
SH_T(kc) -- Momentary swap when held, kc when tapped
Anyway:
I've set PERMISSIVE_HOLD for my keyboard but I am still having issues. Perhaps I will try to refine tests to isolate the problem.
Another issue I found is that tapping OSM breaks subsequent SH_T (with HOLD_ON_OTHER_KEY_PRESS).
TEST_F(SH, SwapHandsAfterOSM) {
TestDriver driver;
auto key_sh_t = KeymapKey(0, 0, 0, SH_T(KC_SPACE));
auto key_a = KeymapKey(0, 1, 0, KC_A);
auto key_b = KeymapKey(0, 2, 0, KC_B);
auto key_m = KeymapKey(0, 3, 0, OSM(MOD_LSFT));
set_keymap({key_sh_t, key_a, key_b, key_m});
debug_enable = true;
debug_keyboard = true;
key_m.press();
idle_for(1);
key_m.release();
idle_for(1);
key_sh_t.press();
idle_for(1);
key_a.press();
EXPECT_REPORT(driver, (key_b.report_code, KC_LEFT_SHIFT));
idle_for(1);
key_sh_t.release();
idle_for(1);
key_a.release();
EXPECT_EMPTY_REPORT(driver);
keyboard_task();
}
results in
...
[ OK ] SH.SwapHandsTapShortHoldSwaps (0 ms)
[ RUN ] SH.SwapHandsAfterOSM
---- action_exec: start -----
EVENT: 0003d(0)
Tapping: Start(Press tap key).
TAPPING_KEY=0003d(0):0
processed: 0003d(0):0
---- action_exec: start -----
EVENT: 0003u(1)
Tapping: First tap(0->1).
TAPPING_KEY=0003d(0):1
ACTION: ACT_LMODS_TAP[2:00] layer_state: 00000000(0) default_layer_state: 00000001(0)
MODS_TAP: Oneshot: start
waiting_buffer_enq: { [1]=0003u(1):1 }
---- action_exec: process waiting_buffer -----
Tapping: Tap release(1)
ACTION: ACT_LMODS_TAP[2:00] layer_state: 00000000(0) default_layer_state: 00000001(0)
TAPPING_KEY=0003u(1):1
processed: waiting_buffer[1] =0003u(1):1
---- action_exec: start -----
EVENT: 0000d(2)
Tapping: Start with interfering other tap.
TAPPING_KEY=0000d(2):0
processed: 0000d(2):0
---- action_exec: start -----
EVENT: 0001d(3)
Tapping: End. No tap. Interfered by pressed key
ACTION: ACT_SWAP_HANDS[0:2C] layer_state: 00000000(0) default_layer_state: 00000001(0)
TAPPING_KEY=0000u(0):0
waiting_buffer_enq: { [2]=0001d(3):0 }
---- action_exec: process waiting_buffer -----
ACTION: ACT_LMODS[0:04] layer_state: 00000000(0) default_layer_state: 00000001(0)
unknown file: Failure
Unexpected mock function call - returning directly.
Function call: send_keyboard_mock(@0x46e0d8 report: (KC_A) [KC_LEFT_SHIFT]
)
Google Mock tried the following 1 expectation, but it didn't match:
tests/swap_hands/test.cpp:86: EXPECT_CALL((driver), send_keyboard_mock(KeyboardReport (key_b.report_code, KC_LEFT_SHIFT)))...
Expected arg #0: is equal to report: (KC_B) [KC_LEFT_SHIFT]
Actual: report: (KC_A) [KC_LEFT_SHIFT]
Expected: to be called once
Actual: never called - unsatisfied and active
keyboard_report: 02 | 04 00 00 00 00 00
processed: waiting_buffer[2] =0001d(3):0
---- action_exec: start -----
EVENT: 0000u(4)
ACTION: ACT_SWAP_HANDS[0:2C] layer_state: 00000000(0) default_layer_state: 00000001(0)
processed: 0000u(4):0
---- action_exec: start -----
EVENT: 0001u(5)
ACTION: ACT_LMODS[0:04] layer_state: 00000000(0) default_layer_state: 00000001(0)
keyboard_report: 00 | 00 00 00 00 00 00
processed: 0001u(5):0
tests/swap_hands/test.cpp:86: Failure
Actual function call count doesn't match EXPECT_CALL((driver), send_keyboard_mock(KeyboardReport (key_b.report_code, KC_LEFT_SHIFT)))...
Expected: to be called once
Actual: never called - unsatisfied and active
layer_state: 00000000(0) to 00000000(0)
SH.SwapHandsAfterOSM failed!
[ LEVEL ] [TIME] [EVENT]
[ INFO ] 0 tapping term is 200ms
[ TRACE ] 0 pressed: OSM(MOD_LSFT)
[ TRACE ] 0 1 keyboard task loop
[ TRACE ] 1 released: OSM(MOD_LSFT) was pressed for 1ms
[ TRACE ] 1 1 keyboard task loop
[ TRACE ] 2 pressed: unknown keycode: 0x562c. Add conversion to generate_identifier
[ TRACE ] 2 1 keyboard task loop
[ TRACE ] 3 pressed: KC_A
[ TRACE ] 3 1 keyboard task loop
[ TRACE ] 3 report: (KC_A) [KC_LEFT_SHIFT]
[ TRACE ] 4 released: unknown keycode: 0x562c. Add conversion to generate_identifier was pressed for 2ms
[ TRACE ] 4 1 keyboard task loop
[ TRACE ] 5 released: KC_A was pressed for 2ms
[ TRACE ] 5 report: empty
[ INFO ] 5 test fixture clean-up start.
[ TRACE ] 5 2000 keyboard task loops
[ TRACE ] 2005 2000 keyboard task loops
[ INFO ] 4005 test fixture clean-up end.
[ FAILED ] SH.SwapHandsAfterOSM (0 ms)
[----------] 3 tests from SH (0 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] SH.SwapHandsAfterOSM
1 FAILED TEST
Posted my tests (one failing, bug or wrong assumptions?) in https://github.com/qmk/qmk_firmware/pull/24420
I have
r
key mirrored toi
. QuickSH_T(KC_SPC) + r
combination results ini
while doing it slowly results ini
as expected. Logs below contain both cases in order.