[Enhancement] Improvements for debounce test coverage + bug fixes for sym_defer_g and sym_eager_pr (#21667) Co-authored-by: Nebuleon <2391500+Nebuleon@users.noreply.github.com>
15 files changed, 458 insertions(+), 23 deletions(-) M platforms/test/timer.c M quantum/debounce/none.c M quantum/debounce/sym_defer_g.c M quantum/debounce/sym_eager_pr.c M quantum/debounce/tests/asym_eager_defer_pk_tests.cpp M quantum/debounce/tests/debounce_test_common.cpp M quantum/debounce/tests/debounce_test_common.h A quantum/debounce/tests/none_tests.cpp M quantum/debounce/tests/rules.mk M quantum/debounce/tests/sym_defer_g_tests.cpp M quantum/debounce/tests/sym_defer_pk_tests.cpp M quantum/debounce/tests/sym_defer_pr_tests.cpp M quantum/debounce/tests/sym_eager_pk_tests.cpp M quantum/debounce/tests/sym_eager_pr_tests.cpp M quantum/debounce/tests/testlist.mk
M platforms/test/timer.c => platforms/test/timer.c +36 -5
@@ 17,34 17,65 @@ #include "timer.h" #include <stdatomic.h> static atomic_uint_least32_t current_time = 0; static atomic_uint_least32_t current_time = 0; static atomic_uint_least32_t async_tick_amount = 0; static atomic_uint_least32_t access_counter = 0; void simulate_async_tick(uint32_t t) { async_tick_amount = t; } uint32_t timer_read_internal(void) { return current_time; } uint32_t current_access_counter(void) { return access_counter; } void reset_access_counter(void) { access_counter = 0; } void timer_init(void) { current_time = 0; current_time = 0; async_tick_amount = 0; access_counter = 0; } void timer_clear(void) { current_time = 0; current_time = 0; async_tick_amount = 0; access_counter = 0; } uint16_t timer_read(void) { return current_time & 0xFFFF; return (uint16_t)timer_read32(); } uint32_t timer_read32(void) { if (access_counter++ > 0) { current_time += async_tick_amount; } return current_time; } uint16_t timer_elapsed(uint16_t last) { return TIMER_DIFF_16(timer_read(), last); } uint32_t timer_elapsed32(uint32_t last) { return TIMER_DIFF_32(timer_read32(), last); } void set_time(uint32_t t) { current_time = t; current_time = t; access_counter = 0; } void advance_time(uint32_t ms) { current_time += ms; access_counter = 0; } void wait_ms(uint32_t ms) {
M quantum/debounce/none.c => quantum/debounce/none.c +8 -2
@@ 20,9 20,15 @@ void debounce_init(uint8_t num_rows) {} bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { bool cooked_changed = memcmp(raw, cooked, sizeof(matrix_row_t) * num_rows) != 0; bool cooked_changed = false; memcpy(cooked, raw, sizeof(matrix_row_t) * num_rows); if (changed) { size_t matrix_size = num_rows * sizeof(matrix_row_t); if (memcmp(cooked, raw, matrix_size) != 0) { memcpy(cooked, raw, matrix_size); cooked_changed = true; } } return cooked_changed; }
M quantum/debounce/sym_defer_g.c => quantum/debounce/sym_defer_g.c +10 -5
@@ 24,6 24,12 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state. # define DEBOUNCE 5 #endif // Maximum debounce: 255ms #if DEBOUNCE > UINT8_MAX # undef DEBOUNCE # define DEBOUNCE UINT8_MAX #endif #if DEBOUNCE > 0 static bool debouncing = false; @@ static fast_timer_t debouncing_time; 36,11 42,10 @@ bool debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool if (changed) { debouncing = true; debouncing_time = timer_read_fast(); } if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) { if (memcmp(cooked, raw, sizeof(matrix_row_t) * num_rows) != 0) { memcpy(cooked, raw, sizeof(matrix_row_t) * num_rows); } else if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) { size_t matrix_size = num_rows * sizeof(matrix_row_t); if (memcmp(cooked, raw, matrix_size) != 0) { memcpy(cooked, raw, matrix_size); cooked_changed = true; } debouncing = false;
M quantum/debounce/sym_eager_pr.c => quantum/debounce/sym_eager_pr.c +2 -2
@@ 128,8 128,8 @@ static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], ui if (existing_row != raw_row) { if (*debounce_pointer == DEBOUNCE_ELAPSED) { *debounce_pointer = DEBOUNCE; cooked[row] = raw_row; cooked_changed |= cooked[row] ^ raw[row]; cooked_changed |= cooked[row] ^ raw_row; cooked[row] = raw_row; counters_need_update = true; } }
M quantum/debounce/tests/asym_eager_defer_pk_tests.cpp => quantum/debounce/tests/asym_eager_defer_pk_tests.cpp +29 -0
@@ 392,3 392,32 @@ TEST_F(DebounceTest, OneKeyDelayedScan8) { time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, /* Release key after 1ms delay */ {1, {{0, 1, UP}}, {}}, /* * Until the eager timer on DOWN is observed to finish, the defer timer * on UP can't start. There's no workaround for this because it's not * possible to debounce an event that isn't being tracked. * * sym_defer_pk has the same problem but the test has to track that the * key changed state so the DOWN timer is always allowed to finish * before starting the UP timer. */ {5, {}, {}}, {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ /* Press key again after 1ms delay */ {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/debounce_test_common.cpp => quantum/debounce/tests/debounce_test_common.cpp +18 -7
@@ 26,8 26,12 @@ extern "C" { #include "debounce.h" #include "timer.h" void set_time(uint32_t t); void advance_time(uint32_t ms); void simulate_async_tick(uint32_t t); void reset_access_counter(void); uint32_t current_access_counter(void); uint32_t timer_read_internal(void); void set_time(uint32_t t); void advance_time(uint32_t ms); } @@ void DebounceTest::addEvents(std::initializer_list<DebounceTestEvent> events) { 58,6 62,7 @@ void DebounceTest::runEventsInternal() { /* Initialise keyboard with start time (offset to avoid testing at 0) and all keys UP */ debounce_init(MATRIX_ROWS); set_time(time_offset_); simulate_async_tick(async_time_jumps_); std::fill(std::begin(input_matrix_), std::end(input_matrix_), 0); std::fill(std::begin(output_matrix_), std::end(output_matrix_), 0); @@ 70,9 75,9 @@ void DebounceTest::runEventsInternal() { advance_time(1); } else { /* Fast forward to the time for this event, calling debounce() with no changes */ ASSERT_LT((time_offset_ + event.time_) - timer_read_fast(), 60000) << "Test tries to advance more than 1 minute of time"; ASSERT_LT((time_offset_ + event.time_) - timer_read_internal(), 60000) << "Test tries to advance more than 1 minute of time"; while (timer_read_fast() != time_offset_ + event.time_) { while (timer_read_internal() != time_offset_ + event.time_) { runDebounce(false); checkCookedMatrix(false, "debounce() modified cooked matrix"); @@ advance_time(1); 124,14 129,20 @@ void DebounceTest::runDebounce(bool changed) { std::copy(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_)); std::copy(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_)); reset_access_counter(); bool cooked_changed = debounce(raw_matrix_, cooked_matrix_, MATRIX_ROWS, changed); if (!std::equal(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_))) { FAIL() << "Fatal error: debounce() modified raw matrix at " << strTime() << "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_) << "\nraw_matrix:\n" << strMatrix(raw_matrix_); } if (std::equal(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_)) && cooked_changed) { FAIL() << "Fatal error: debounce() did detect a wrong cooked matrix change at " << strTime() << "\noutput_matrix: cooked_changed=" << cooked_changed << "\n" << strMatrix(output_matrix_) << "\ncooked_matrix:\n" << strMatrix(cooked_matrix_); if (std::equal(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_)) == cooked_changed) { FAIL() << "Fatal error: debounce() reported a wrong cooked matrix change result at " << strTime() << "\noutput_matrix: cooked_changed=" << cooked_changed << "\n" << strMatrix(output_matrix_) << "\ncooked_matrix:\n" << strMatrix(cooked_matrix_); } if (current_access_counter() > 1) { FAIL() << "Fatal error: debounce() read the timer multiple times, which is not allowed, at " << strTime() << "\ntimer: access_count=" << current_access_counter() << "\noutput_matrix: cooked_changed=" << cooked_changed << "\n" << strMatrix(output_matrix_) << "\ncooked_matrix:\n" << strMatrix(cooked_matrix_); } } @@ 144,7 155,7 @@ void DebounceTest::checkCookedMatrix(bool changed, const std::string &error_mess std::string DebounceTest::strTime() { std::stringstream text; text << "time " << (timer_read_fast() - time_offset_) << " (extra_iterations=" << extra_iterations_ << ", auto_advance_time=" << auto_advance_time_ << ")"; text << "time " << (timer_read_internal() - time_offset_) << " (extra_iterations=" << extra_iterations_ << ", auto_advance_time=" << auto_advance_time_ << ")"; return text.str(); }
M quantum/debounce/tests/debounce_test_common.h => quantum/debounce/tests/debounce_test_common.h +3 -2
@@ 54,8 54,9 @@ class DebounceTest : public ::testing::Test { void addEvents(std::initializer_list<DebounceTestEvent> events); void runEvents(); fast_timer_t time_offset_ = 7777; bool time_jumps_ = false; fast_timer_t time_offset_ = 7777; bool time_jumps_ = false; fast_timer_t async_time_jumps_ = 0; private: static bool directionValue(Direction direction);
A quantum/debounce/tests/none_tests.cpp => quantum/debounce/tests/none_tests.cpp +256 -0
@@ 0,0 1,256 @@ /* Copyright 2021 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "gtest/gtest.h" #include "debounce_test_common.h" TEST_F(DebounceTest, OneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, /* 0ms delay (fast scan rate) */ {5, {{0, 1, UP}}, {{0, 1, UP}}}, {10, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, OneKeyShort2) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, /* 1ms delay */ {6, {{0, 1, UP}}, {{0, 1, UP}}}, {11, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, OneKeyShort3) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, /* 2ms delay */ {7, {{0, 1, UP}}, {{0, 1, UP}}}, {12, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, OneKeyTooQuick1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, /* Release key exactly on the debounce time */ {5, {{0, 1, UP}}, {{0, 1, UP}}}, }); runEvents(); } TEST_F(DebounceTest, OneKeyTooQuick2) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, {6, {{0, 1, UP}}, {{0, 1, UP}}}, /* Press key exactly on the debounce time */ {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, }); runEvents(); } TEST_F(DebounceTest, OneKeyBouncing1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {1, {{0, 1, UP}}, {{0, 1, UP}}}, {2, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {3, {{0, 1, UP}}, {{0, 1, UP}}}, {4, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {{0, 1, UP}}, {{0, 1, UP}}}, {6, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {11, {{0, 1, UP}}, {{0, 1, UP}}}, /* 5ms after DOWN at time 7 */ }); runEvents(); } TEST_F(DebounceTest, OneKeyBouncing2) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, {6, {{0, 1, UP}}, {{0, 1, UP}}}, {7, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {8, {{0, 1, UP}}, {{0, 1, UP}}}, {9, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {10, {{0, 1, UP}}, {{0, 1, UP}}}, {15, {}, {}}, /* 5ms after UP at time 10 */ }); runEvents(); } TEST_F(DebounceTest, OneKeyLong) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, {25, {{0, 1, UP}}, {{0, 1, UP}}}, {30, {}, {}}, {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {55, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, TwoKeysShort) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {1, {{0, 2, DOWN}}, {{0, 2, DOWN}}}, {6, {}, {}}, {7, {{0, 1, UP}}, {{0, 1, UP}}}, {8, {{0, 2, UP}}, {{0, 2, UP}}}, {13, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, TwoKeysSimultaneous1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}}, {5, {}, {}}, {6, {{0, 1, UP}, {0, 2, UP}}, {{0, 1, UP}, {0, 2, UP}}}, {11, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, TwoKeysSimultaneous2) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {1, {{0, 2, DOWN}}, {{0, 2, DOWN}}}, {5, {}, {}}, {6, {}, {}}, {7, {{0, 1, UP}}, {{0, 1, UP}}}, {8, {{0, 2, UP}}, {{0, 2, UP}}}, {13, {}, {}}, }); runEvents(); } TEST_F(DebounceTest, OneKeyDelayedScan1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, /* Processing is very late */ {300, {}, {}}, /* Immediately release key */ {300, {{0, 1, UP}}, {{0, 1, UP}}}, {305, {}, {}}, }); time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, OneKeyDelayedScan2) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, /* Processing is very late */ {300, {}, {}}, /* Release key after 1ms */ {301, {{0, 1, UP}}, {{0, 1, UP}}}, {306, {}, {}}, }); time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, OneKeyDelayedScan3) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, /* Release key before debounce expires */ {300, {{0, 1, UP}}, {{0, 1, UP}}}, }); time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, OneKeyDelayedScan4) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, /* Processing is a bit late */ {50, {}, {}}, /* Release key after 1ms */ {51, {{0, 1, UP}}, {{0, 1, UP}}}, {56, {}, {}}, }); time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {5, {}, {}}, /* 0ms delay (fast scan rate) */ {5, {{0, 1, UP}}, {{0, 1, UP}}}, {10, {}, {}}, }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/rules.mk => quantum/debounce/tests/rules.mk +5 -0
@@ 18,6 18,11 @@ DEBOUNCE_COMMON_DEFS := -DMATRIX_ROWS=4 -DMATRIX_COLS=10 -DDEBOUNCE=5 DEBOUNCE_COMMON_SRC := $(QUANTUM_PATH)/debounce/tests/debounce_test_common.cpp \ $(PLATFORM_PATH)/$(PLATFORM_KEY)/timer.c debounce_none_DEFS := $(DEBOUNCE_COMMON_DEFS) debounce_none_SRC := $(DEBOUNCE_COMMON_SRC) \ $(QUANTUM_PATH)/debounce/none.c \ $(QUANTUM_PATH)/debounce/tests/none_tests.cpp debounce_sym_defer_g_DEFS := $(DEBOUNCE_COMMON_DEFS) debounce_sym_defer_g_SRC := $(DEBOUNCE_COMMON_SRC) \ $(QUANTUM_PATH)/debounce/sym_defer_g.c \
M quantum/debounce/tests/sym_defer_g_tests.cpp => quantum/debounce/tests/sym_defer_g_tests.cpp +18 -0
@@ 236,3 236,21 @@ TEST_F(DebounceTest, OneKeyDelayedScan4) { time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {}}, {5, {}, {{0, 1, DOWN}}}, /* 0ms delay (fast scan rate) */ {5, {{0, 1, UP}}, {}}, {10, {}, {{0, 1, UP}}}, }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/sym_defer_pk_tests.cpp => quantum/debounce/tests/sym_defer_pk_tests.cpp +18 -0
@@ 238,3 238,21 @@ TEST_F(DebounceTest, OneKeyDelayedScan4) { time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {}}, {5, {}, {{0, 1, DOWN}}}, /* 0ms delay (fast scan rate) */ {5, {{0, 1, UP}}, {}}, {10, {}, {{0, 1, UP}}}, }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/sym_defer_pr_tests.cpp => quantum/debounce/tests/sym_defer_pr_tests.cpp +18 -0
@@ 236,3 236,21 @@ TEST_F(DebounceTest, OneKeyDelayedScan4) { time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {}}, {5, {}, {{0, 1, DOWN}}}, /* 0ms delay (fast scan rate) */ {5, {{0, 1, UP}}, {}}, {10, {}, {{0, 1, UP}}}, }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/sym_eager_pk_tests.cpp => quantum/debounce/tests/sym_eager_pk_tests.cpp +18 -0
@@ 251,3 251,21 @@ TEST_F(DebounceTest, OneKeyDelayedScan6) { time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {1, {{0, 1, UP}}, {}}, {5, {}, {{0, 1, UP}}}, /* Press key again after 1ms delay (debounce has not yet finished) */ {6, {{0, 1, DOWN}}, {}}, {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/sym_eager_pr_tests.cpp => quantum/debounce/tests/sym_eager_pr_tests.cpp +18 -0
@@ 297,3 297,21 @@ TEST_F(DebounceTest, OneKeyDelayedScan6) { time_jumps_ = true; runEvents(); } TEST_F(DebounceTest, AsyncTickOneKeyShort1) { addEvents({ /* Time, Inputs, Outputs */ {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, {1, {{0, 1, UP}}, {}}, {5, {}, {{0, 1, UP}}}, /* Press key again after 1ms delay (debounce has not yet finished) */ {6, {{0, 1, DOWN}}, {}}, {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ }); /* * Debounce implementations should never read the timer more than once per invocation */ async_time_jumps_ = DEBOUNCE; runEvents(); }
M quantum/debounce/tests/testlist.mk => quantum/debounce/tests/testlist.mk +1 -0