qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

test_fixture.cpp (6020B)


      1 #include "test_fixture.hpp"
      2 #include <algorithm>
      3 #include <cstdint>
      4 #include <cstdio>
      5 #include <cstdlib>
      6 #include "gmock/gmock-cardinalities.h"
      7 #include "gmock/gmock.h"
      8 #include "gtest/gtest.h"
      9 #include "keyboard_report_util.hpp"
     10 #include "mouse_report_util.hpp"
     11 #include "keycode.h"
     12 #include "test_driver.hpp"
     13 #include "test_logger.hpp"
     14 #include "test_matrix.h"
     15 #include "test_keymap_key.hpp"
     16 #include "timer.h"
     17 
     18 extern "C" {
     19 #include "action.h"
     20 #include "action_tapping.h"
     21 #include "action_util.h"
     22 #include "action_layer.h"
     23 #include "debug.h"
     24 #include "eeconfig.h"
     25 #include "keyboard.h"
     26 
     27 void set_time(uint32_t t);
     28 void advance_time(uint32_t ms);
     29 }
     30 
     31 using testing::_;
     32 
     33 /* This is used for dynamic dispatching keymap_key_to_keycode calls to the current active test_fixture. */
     34 TestFixture* TestFixture::m_this = nullptr;
     35 
     36 /* Override weak QMK function to allow the usage of isolated per-test keymaps in unit-tests.
     37  * The actual call is dynamicaly dispatched to the current active test fixture, which in turn has it's own keymap. */
     38 extern "C" uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t position) {
     39     uint16_t keycode;
     40     TestFixture::m_this->get_keycode(layer, position, &keycode);
     41     return keycode;
     42 }
     43 
     44 void TestFixture::SetUpTestCase() {
     45     test_logger.info() << "test fixture setup-up start." << std::endl;
     46 
     47     // The following is enough to bootstrap the values set in main
     48     eeconfig_init_quantum();
     49     eeconfig_update_debug(&debug_config);
     50 
     51     TestDriver driver;
     52     keyboard_init();
     53 
     54     test_logger.info() << "test fixture setup-up end." << std::endl;
     55 }
     56 
     57 void TestFixture::TearDownTestCase() {}
     58 
     59 TestFixture::TestFixture() {
     60     m_this = this;
     61     timer_clear();
     62     keyrecord_t empty_keyrecord = {0};
     63     test_logger.info() << "tapping term is " << +GET_TAPPING_TERM(KC_TRANSPARENT, &empty_keyrecord) << "ms" << std::endl;
     64 }
     65 
     66 TestFixture::~TestFixture() {
     67     test_logger.info() << "test fixture clean-up start." << std::endl;
     68     TestDriver driver;
     69 
     70     /* Reset keyboard state. */
     71     clear_all_keys();
     72 
     73 #ifdef MOUSEKEY_ENABLE
     74     EXPECT_EMPTY_MOUSE_REPORT(driver);
     75 #endif
     76     clear_keyboard();
     77 
     78     clear_oneshot_mods();
     79     clear_oneshot_locked_mods();
     80     reset_oneshot_layer();
     81 
     82     layer_clear();
     83 
     84 #if defined(SWAP_HANDS_ENABLE)
     85     clear_oneshot_swaphands();
     86 #endif
     87 
     88     idle_for(TAPPING_TERM * 10);
     89     VERIFY_AND_CLEAR(driver);
     90 
     91     /* Verify that the matrix really is cleared */
     92     EXPECT_NO_REPORT(driver);
     93     idle_for(TAPPING_TERM * 10);
     94     VERIFY_AND_CLEAR(driver);
     95     m_this = nullptr;
     96 
     97     test_logger.info() << "test fixture clean-up end." << std::endl;
     98     print_test_log();
     99 }
    100 
    101 void TestFixture::add_key(KeymapKey key) {
    102     if (this->find_key(key.layer, key.position)) {
    103         FAIL() << "key is already mapped for layer " << +key.layer << " and (column,row) (" << +key.position.col << "," << +key.position.row << ")";
    104     }
    105 
    106     this->keymap.push_back(key);
    107 }
    108 
    109 void TestFixture::tap_key(KeymapKey key, unsigned delay_ms) {
    110     key.press();
    111     idle_for(delay_ms);
    112     key.release();
    113     run_one_scan_loop();
    114 }
    115 
    116 void TestFixture::tap_combo(const std::vector<KeymapKey>& chord_keys, unsigned delay_ms) {
    117     for (KeymapKey key : chord_keys) { // Press each key.
    118         key.press();
    119         run_one_scan_loop();
    120     }
    121 
    122     if (delay_ms > 1) {
    123         idle_for(delay_ms - 1);
    124     }
    125 
    126     for (KeymapKey key : chord_keys) { // Release each key.
    127         key.release();
    128         run_one_scan_loop();
    129     }
    130 }
    131 
    132 void TestFixture::set_keymap(std::initializer_list<KeymapKey> keys) {
    133     this->keymap.clear();
    134     for (auto& key : keys) {
    135         add_key(key);
    136     }
    137 }
    138 
    139 const KeymapKey* TestFixture::find_key(layer_t layer, keypos_t position) const {
    140     auto keymap_key_predicate = [&](KeymapKey candidate) { return candidate.layer == layer && candidate.position.col == position.col && candidate.position.row == position.row; };
    141 
    142     auto result = std::find_if(this->keymap.begin(), this->keymap.end(), keymap_key_predicate);
    143 
    144     if (result != std::end(this->keymap)) {
    145         return &(*result);
    146     }
    147     return nullptr;
    148 }
    149 
    150 void TestFixture::get_keycode(const layer_t layer, const keypos_t position, uint16_t* result) const {
    151     bool key_is_out_of_bounds = position.col >= MATRIX_COLS && position.row >= MATRIX_ROWS;
    152 
    153     if (key_is_out_of_bounds) {
    154         /* See if this is done in hardware as well, because this is 100% out of bounds reads on all QMK keebs out there. */
    155         auto msg = [&]() {
    156             std::stringstream msg;
    157             msg << "keycode for position (" << +position.col << "," << +position.row << ") requested! This is out of bounds." << std::endl;
    158             return msg.str();
    159         }();
    160 
    161         *result = KC_NO;
    162         test_logger.error() << msg;
    163         EXPECT_FALSE(key_is_out_of_bounds) << msg;
    164         return;
    165     }
    166 
    167     if (auto key = this->find_key(layer, position)) {
    168         *result = key->code;
    169         return;
    170     }
    171 
    172     FAIL() << "no key is mapped for layer " << +layer << " and (column,row) " << +position.col << "," << +position.row << ")";
    173 }
    174 
    175 void TestFixture::run_one_scan_loop() {
    176     this->idle_for(1);
    177 }
    178 
    179 void TestFixture::idle_for(unsigned time) {
    180     test_logger.trace() << +time << " keyboard task " << (time > 1 ? "loops" : "loop") << std::endl;
    181     for (unsigned i = 0; i < time; i++) {
    182         keyboard_task();
    183         housekeeping_task();
    184         advance_time(1);
    185     }
    186 }
    187 
    188 void TestFixture::print_test_log() const {
    189     const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
    190     if (HasFailure()) {
    191         std::cerr << test_info->test_case_name() << "." << test_info->name() << " failed!" << std::endl;
    192         test_logger.print_header();
    193         test_logger.print_log();
    194     }
    195     test_logger.reset();
    196 }
    197 
    198 void TestFixture::expect_layer_state(layer_t layer_state) const {
    199     test_logger.trace() << "layer state: (" << +layer_state << ") highest layer bit: (" << +get_highest_layer(layer_state) << ")" << std::endl;
    200     EXPECT_TRUE(layer_state_is(layer_state));
    201 }