Procházet zdrojové kódy

Merge pull request #1409 from fredizzimo/full_unit_test

Take first baby steps towards testing the whole QMK
Jack Humbert před 8 roky
rodič
revize
e951317acb

+ 1 - 1
Makefile

@@ -419,7 +419,7 @@ define BUILD_TEST
     MAKE_TARGET := $2
     COMMAND := $1
     MAKE_CMD := $$(MAKE) -r -R -C $(ROOT_DIR) -f build_test.mk $$(MAKE_TARGET)
-    MAKE_VARS := TEST=$$(TEST_NAME)
+    MAKE_VARS := TEST=$$(TEST_NAME) FULL_TESTS=$$(FULL_TESTS)
     MAKE_MSG := $$(MSG_MAKE_TEST)
     $$(eval $$(call BUILD))
     ifneq ($$(MAKE_TARGET),clean)

+ 30 - 0
build_full_test.mk

@@ -0,0 +1,30 @@
+# Copyright 2017 Fred Sundvik
+#
+# 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 $(TMK_PATH)/protocol.mk
+
+TEST_PATH=tests/$(TEST)
+
+$(TEST)_SRC= \
+	$(TEST_PATH)/test.cpp \
+	$(TMK_COMMON_SRC) \
+	$(QUANTUM_SRC) \
+	tests/test_common/matrix.c \
+	tests/test_common/test_driver.cpp \
+	tests/test_common/keyboard_report_util.cpp \
+	tests/test_common/test_fixture.cpp
+$(TEST)_DEFS=$(TMK_COMMON_DEFS)
+$(TEST)_CONFIG=$(TEST_PATH)/config.h
+VPATH+=$(TOP_DIR)/tests/test_common

+ 3 - 134
build_keyboard.mk

@@ -31,8 +31,6 @@ $(error MASTER does not have a valid value(left/right))
     endif
 endif
 
-
-
 KEYBOARD_PATH := keyboards/$(KEYBOARD)
 KEYBOARD_C := $(KEYBOARD_PATH)/$(KEYBOARD).c
 
@@ -42,7 +40,6 @@ else
     $(error "$(KEYBOARD_C)" does not exist)
 endif
 
-
 ifneq ($(SUBPROJECT),)
     SUBPROJECT_PATH := keyboards/$(KEYBOARD)/$(SUBPROJECT)
     SUBPROJECT_C := $(SUBPROJECT_PATH)/$(SUBPROJECT).c
@@ -118,141 +115,12 @@ endif
 # # project specific files
 SRC += $(KEYBOARD_C) \
     $(KEYMAP_C) \
-    $(QUANTUM_DIR)/quantum.c \
-    $(QUANTUM_DIR)/keymap_common.c \
-    $(QUANTUM_DIR)/keycode_config.c \
-    $(QUANTUM_DIR)/process_keycode/process_leader.c
+    $(QUANTUM_SRC)
 
 ifneq ($(SUBPROJECT),)
     SRC += $(SUBPROJECT_C)
 endif
 
-ifndef CUSTOM_MATRIX
-    SRC += $(QUANTUM_DIR)/matrix.c
-endif
-
-ifeq ($(strip $(API_SYSEX_ENABLE)), yes)
-    OPT_DEFS += -DAPI_SYSEX_ENABLE
-    SRC += $(QUANTUM_DIR)/api/api_sysex.c
-    OPT_DEFS += -DAPI_ENABLE
-    SRC += $(QUANTUM_DIR)/api.c
-    MIDI_ENABLE=yes
-endif
-
-MUSIC_ENABLE := 0
-
-ifeq ($(strip $(AUDIO_ENABLE)), yes)
-    OPT_DEFS += -DAUDIO_ENABLE
-    MUSIC_ENABLE := 1
-    SRC += $(QUANTUM_DIR)/process_keycode/process_audio.c
-    SRC += $(QUANTUM_DIR)/audio/audio.c
-    SRC += $(QUANTUM_DIR)/audio/voices.c
-    SRC += $(QUANTUM_DIR)/audio/luts.c
-endif
-
-ifeq ($(strip $(MIDI_ENABLE)), yes)
-    OPT_DEFS += -DMIDI_ENABLE
-    MUSIC_ENABLE := 1
-    SRC += $(QUANTUM_DIR)/process_keycode/process_midi.c
-endif
-
-ifeq ($(MUSIC_ENABLE), 1)
-    SRC += $(QUANTUM_DIR)/process_keycode/process_music.c
-endif
-
-ifeq ($(strip $(COMBO_ENABLE)), yes)
-    OPT_DEFS += -DCOMBO_ENABLE
-    SRC += $(QUANTUM_DIR)/process_keycode/process_combo.c
-endif
-
-ifeq ($(strip $(VIRTSER_ENABLE)), yes)
-    OPT_DEFS += -DVIRTSER_ENABLE
-endif
-
-ifeq ($(strip $(FAUXCLICKY_ENABLE)), yes)
-    OPT_DEFS += -DFAUXCLICKY_ENABLE
-    SRC += $(QUANTUM_DIR)/fauxclicky.c
-endif
-
-ifeq ($(strip $(UCIS_ENABLE)), yes)
-    OPT_DEFS += -DUCIS_ENABLE
-    UNICODE_COMMON = yes
-    SRC += $(QUANTUM_DIR)/process_keycode/process_ucis.c
-endif
-
-ifeq ($(strip $(UNICODEMAP_ENABLE)), yes)
-    OPT_DEFS += -DUNICODEMAP_ENABLE
-    UNICODE_COMMON = yes
-    SRC += $(QUANTUM_DIR)/process_keycode/process_unicodemap.c
-endif
-
-ifeq ($(strip $(UNICODE_ENABLE)), yes)
-    OPT_DEFS += -DUNICODE_ENABLE
-    UNICODE_COMMON = yes
-    SRC += $(QUANTUM_DIR)/process_keycode/process_unicode.c
-endif
-
-ifeq ($(strip $(UNICODE_COMMON)), yes)
-    SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c
-endif
-
-ifeq ($(strip $(RGBLIGHT_ENABLE)), yes)
-    OPT_DEFS += -DRGBLIGHT_ENABLE
-    SRC += $(QUANTUM_DIR)/light_ws2812.c
-    SRC += $(QUANTUM_DIR)/rgblight.c
-    CIE1931_CURVE = yes
-    LED_BREATHING_TABLE = yes
-endif
-
-ifeq ($(strip $(TAP_DANCE_ENABLE)), yes)
-    OPT_DEFS += -DTAP_DANCE_ENABLE
-    SRC += $(QUANTUM_DIR)/process_keycode/process_tap_dance.c
-endif
-
-ifeq ($(strip $(PRINTING_ENABLE)), yes)
-    OPT_DEFS += -DPRINTING_ENABLE
-    SRC += $(QUANTUM_DIR)/process_keycode/process_printer.c
-    SRC += $(TMK_DIR)/protocol/serial_uart.c
-endif
-
-ifeq ($(strip $(SERIAL_LINK_ENABLE)), yes)
-    SRC += $(patsubst $(QUANTUM_PATH)/%,%,$(SERIAL_SRC))
-    OPT_DEFS += $(SERIAL_DEFS)
-    VAPTH += $(SERIAL_PATH)
-endif
-
-ifneq ($(strip $(VARIABLE_TRACE)),)
-    SRC += $(QUANTUM_DIR)/variable_trace.c
-    OPT_DEFS += -DNUM_TRACED_VARIABLES=$(strip $(VARIABLE_TRACE))
-ifneq ($(strip $(MAX_VARIABLE_TRACE_SIZE)),)
-    OPT_DEFS += -DMAX_VARIABLE_TRACE_SIZE=$(strip $(MAX_VARIABLE_TRACE_SIZE))
-endif
-endif
-
-ifeq ($(strip $(LCD_ENABLE)), yes)
-    CIE1931_CURVE = yes
-endif
-
-ifeq ($(strip $(BACKLIGHT_ENABLE)), yes)
-    ifeq ($(strip $(VISUALIZER_ENABLE)), yes)
-        CIE1931_CURVE = yes
-    endif
-endif
-
-ifeq ($(strip $(CIE1931_CURVE)), yes)
-    OPT_DEFS += -DUSE_CIE1931_CURVE
-    LED_TABLES = yes
-endif
-
-ifeq ($(strip $(LED_BREATHING_TABLE)), yes)
-    OPT_DEFS += -DUSE_LED_BREATHING_TABLE
-    LED_TABLES = yes
-endif
-
-ifeq ($(strip $(LED_TABLES)), yes)
-    SRC += $(QUANTUM_DIR)/led_tables.c
-endif
-
 # Optimize size but this may cause error "relocation truncated to fit"
 #EXTRALDFLAGS = -Wl,--relax
 
@@ -264,9 +132,10 @@ endif
 VPATH += $(KEYBOARD_PATH)
 VPATH += $(COMMON_VPATH)
 
+include common_features.mk
 include $(TMK_PATH)/protocol.mk
-
 include $(TMK_PATH)/common.mk
+
 SRC += $(TMK_COMMON_SRC)
 OPT_DEFS += $(TMK_COMMON_DEFS)
 EXTRALDFLAGS += $(TMK_COMMON_LDFLAGS)

+ 10 - 0
build_test.mk

@@ -40,13 +40,23 @@ VPATH +=\
 all: elf
 
 VPATH += $(COMMON_VPATH)
+PLATFORM:=TEST
 
+ifneq ($(filter $(FULL_TESTS),$(TEST)),)
+include tests/$(TEST)/rules.mk
+endif
+
+include common_features.mk
 include $(TMK_PATH)/common.mk
 include $(QUANTUM_PATH)/serial_link/tests/rules.mk
+ifneq ($(filter $(FULL_TESTS),$(TEST)),)
+include build_full_test.mk
+endif
 
 $(TEST_OBJ)/$(TEST)_SRC := $($(TEST)_SRC)
 $(TEST_OBJ)/$(TEST)_INC := $($(TEST)_INC) $(VPATH) $(GTEST_INC)
 $(TEST_OBJ)/$(TEST)_DEFS := $($(TEST)_DEFS)
+$(TEST_OBJ)/$(TEST)_CONFIG := $($(TEST)_CONFIG)
 
 include $(TMK_PATH)/native.mk
 include $(TMK_PATH)/rules.mk

+ 1 - 8
common.mk

@@ -11,17 +11,10 @@ QUANTUM_PATH = $(TOP_DIR)/$(QUANTUM_DIR)
 
 BUILD_DIR := $(TOP_DIR)/.build
 
-SERIAL_DIR := $(QUANTUM_DIR)/serial_link
-SERIAL_PATH := $(QUANTUM_PATH)/serial_link
-SERIAL_SRC := $(wildcard $(SERIAL_PATH)/protocol/*.c)
-SERIAL_SRC += $(wildcard $(SERIAL_PATH)/system/*.c)
-SERIAL_DEFS += -DSERIAL_LINK_ENABLE
-
 COMMON_VPATH := $(TOP_DIR)
 COMMON_VPATH += $(TMK_PATH)
 COMMON_VPATH += $(QUANTUM_PATH)
 COMMON_VPATH += $(QUANTUM_PATH)/keymap_extras
 COMMON_VPATH += $(QUANTUM_PATH)/audio
 COMMON_VPATH += $(QUANTUM_PATH)/process_keycode
-COMMON_VPATH += $(QUANTUM_PATH)/api
-COMMON_VPATH += $(SERIAL_PATH)
+COMMON_VPATH += $(QUANTUM_PATH)/api

+ 153 - 0
common_features.mk

@@ -0,0 +1,153 @@
+# Copyright 2017 Fred Sundvik
+#
+# 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/>.
+
+SERIAL_DIR := $(QUANTUM_DIR)/serial_link
+SERIAL_PATH := $(QUANTUM_PATH)/serial_link
+SERIAL_SRC := $(wildcard $(SERIAL_PATH)/protocol/*.c)
+SERIAL_SRC += $(wildcard $(SERIAL_PATH)/system/*.c)
+SERIAL_DEFS += -DSERIAL_LINK_ENABLE
+COMMON_VPATH += $(SERIAL_PATH)
+
+ifeq ($(strip $(API_SYSEX_ENABLE)), yes)
+    OPT_DEFS += -DAPI_SYSEX_ENABLE
+    SRC += $(QUANTUM_DIR)/api/api_sysex.c
+    OPT_DEFS += -DAPI_ENABLE
+    SRC += $(QUANTUM_DIR)/api.c
+    MIDI_ENABLE=yes
+endif
+
+MUSIC_ENABLE := 0
+
+ifeq ($(strip $(AUDIO_ENABLE)), yes)
+    OPT_DEFS += -DAUDIO_ENABLE
+    MUSIC_ENABLE := 1
+    SRC += $(QUANTUM_DIR)/process_keycode/process_audio.c
+    SRC += $(QUANTUM_DIR)/audio/audio.c
+    SRC += $(QUANTUM_DIR)/audio/voices.c
+    SRC += $(QUANTUM_DIR)/audio/luts.c
+endif
+
+ifeq ($(strip $(MIDI_ENABLE)), yes)
+    OPT_DEFS += -DMIDI_ENABLE
+    MUSIC_ENABLE := 1
+    SRC += $(QUANTUM_DIR)/process_keycode/process_midi.c
+endif
+
+ifeq ($(MUSIC_ENABLE), 1)
+    SRC += $(QUANTUM_DIR)/process_keycode/process_music.c
+endif
+
+ifeq ($(strip $(COMBO_ENABLE)), yes)
+    OPT_DEFS += -DCOMBO_ENABLE
+    SRC += $(QUANTUM_DIR)/process_keycode/process_combo.c
+endif
+
+ifeq ($(strip $(VIRTSER_ENABLE)), yes)
+    OPT_DEFS += -DVIRTSER_ENABLE
+endif
+
+ifeq ($(strip $(FAUXCLICKY_ENABLE)), yes)
+    OPT_DEFS += -DFAUXCLICKY_ENABLE
+    SRC += $(QUANTUM_DIR)/fauxclicky.c
+endif
+
+ifeq ($(strip $(UCIS_ENABLE)), yes)
+    OPT_DEFS += -DUCIS_ENABLE
+    UNICODE_COMMON = yes
+    SRC += $(QUANTUM_DIR)/process_keycode/process_ucis.c
+endif
+
+ifeq ($(strip $(UNICODEMAP_ENABLE)), yes)
+    OPT_DEFS += -DUNICODEMAP_ENABLE
+    UNICODE_COMMON = yes
+    SRC += $(QUANTUM_DIR)/process_keycode/process_unicodemap.c
+endif
+
+ifeq ($(strip $(UNICODE_ENABLE)), yes)
+    OPT_DEFS += -DUNICODE_ENABLE
+    UNICODE_COMMON = yes
+    SRC += $(QUANTUM_DIR)/process_keycode/process_unicode.c
+endif
+
+ifeq ($(strip $(UNICODE_COMMON)), yes)
+    SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c
+endif
+
+ifeq ($(strip $(RGBLIGHT_ENABLE)), yes)
+    OPT_DEFS += -DRGBLIGHT_ENABLE
+    SRC += $(QUANTUM_DIR)/light_ws2812.c
+    SRC += $(QUANTUM_DIR)/rgblight.c
+    CIE1931_CURVE = yes
+    LED_BREATHING_TABLE = yes
+endif
+
+ifeq ($(strip $(TAP_DANCE_ENABLE)), yes)
+    OPT_DEFS += -DTAP_DANCE_ENABLE
+    SRC += $(QUANTUM_DIR)/process_keycode/process_tap_dance.c
+endif
+
+ifeq ($(strip $(PRINTING_ENABLE)), yes)
+    OPT_DEFS += -DPRINTING_ENABLE
+    SRC += $(QUANTUM_DIR)/process_keycode/process_printer.c
+    SRC += $(TMK_DIR)/protocol/serial_uart.c
+endif
+
+ifeq ($(strip $(SERIAL_LINK_ENABLE)), yes)
+    SRC += $(patsubst $(QUANTUM_PATH)/%,%,$(SERIAL_SRC))
+    OPT_DEFS += $(SERIAL_DEFS)
+    VAPTH += $(SERIAL_PATH)
+endif
+
+ifneq ($(strip $(VARIABLE_TRACE)),)
+    SRC += $(QUANTUM_DIR)/variable_trace.c
+    OPT_DEFS += -DNUM_TRACED_VARIABLES=$(strip $(VARIABLE_TRACE))
+ifneq ($(strip $(MAX_VARIABLE_TRACE_SIZE)),)
+    OPT_DEFS += -DMAX_VARIABLE_TRACE_SIZE=$(strip $(MAX_VARIABLE_TRACE_SIZE))
+endif
+endif
+
+ifeq ($(strip $(LCD_ENABLE)), yes)
+    CIE1931_CURVE = yes
+endif
+
+ifeq ($(strip $(BACKLIGHT_ENABLE)), yes)
+    ifeq ($(strip $(VISUALIZER_ENABLE)), yes)
+        CIE1931_CURVE = yes
+    endif
+endif
+
+ifeq ($(strip $(CIE1931_CURVE)), yes)
+    OPT_DEFS += -DUSE_CIE1931_CURVE
+    LED_TABLES = yes
+endif
+
+ifeq ($(strip $(LED_BREATHING_TABLE)), yes)
+    OPT_DEFS += -DUSE_LED_BREATHING_TABLE
+    LED_TABLES = yes
+endif
+
+ifeq ($(strip $(LED_TABLES)), yes)
+    SRC += $(QUANTUM_DIR)/led_tables.c
+endif
+
+QUANTUM_SRC:= \
+    $(QUANTUM_DIR)/quantum.c \
+    $(QUANTUM_DIR)/keymap_common.c \
+    $(QUANTUM_DIR)/keycode_config.c \
+    $(QUANTUM_DIR)/process_keycode/process_leader.c
+
+ifndef CUSTOM_MATRIX
+    QUANTUM_SRC += $(QUANTUM_DIR)/matrix.c
+endif

+ 4 - 0
testlist.mk

@@ -1,3 +1,6 @@
+TEST_LIST = $(notdir $(patsubst %/rules.mk,%,$(wildcard $(ROOT_DIR)/tests/*/rules.mk)))
+FULL_TESTS := $(TEST_LIST)
+
 include $(ROOT_DIR)/quantum/serial_link/tests/testlist.mk
 
 define VALIDATE_TEST_LIST
@@ -10,4 +13,5 @@ define VALIDATE_TEST_LIST
     endif
 endef
 
+
 $(eval $(call VALIDATE_TEST_LIST,$(firstword $(TEST_LIST)),$(wordlist 2,9999,$(TEST_LIST))))

+ 24 - 0
tests/basic/config.h

@@ -0,0 +1,24 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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/>.
+ */
+
+#ifndef TESTS_BASIC_CONFIG_H_
+#define TESTS_BASIC_CONFIG_H_
+
+#define MATRIX_ROWS 2
+#define MATRIX_COLS 2
+
+
+#endif /* TESTS_BASIC_CONFIG_H_ */

+ 16 - 0
tests/basic/rules.mk

@@ -0,0 +1,16 @@
+# Copyright 2017 Fred Sundvik
+#
+# 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/>.
+
+CUSTOM_MATRIX=yes

+ 60 - 0
tests/basic/test.cpp

@@ -0,0 +1,60 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "gmock/gmock.h"
+
+#include "quantum.h"
+#include "test_driver.h"
+#include "test_matrix.h"
+#include "keyboard_report_util.h"
+#include "test_fixture.h"
+
+using testing::_;
+using testing::Return;
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+	[0] = {
+	    {KC_A, KC_B},
+	    {KC_C, KC_D}
+	},
+};
+
+class KeyPress : public TestFixture {};
+
+TEST_F(KeyPress, SendKeyboardIsNotCalledWhenNoKeyIsPressed) {
+    TestDriver driver;
+    EXPECT_CALL(driver, send_keyboard_mock(_)).Times(0);
+    keyboard_task();
+}
+
+TEST_F(KeyPress, CorrectKeyIsReportedWhenPressed) {
+    TestDriver driver;
+    press_key(0, 0);
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A)));
+    keyboard_task();
+}
+
+TEST_F(KeyPress, CorrectKeysAreReportedWhenTwoKeysArePressed) {
+    TestDriver driver;
+    press_key(1, 0);
+    press_key(0, 1);
+    //Note that QMK only processes one key at a time
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_B)));
+    keyboard_task();
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_B, KC_C)));
+    keyboard_task();
+}

+ 76 - 0
tests/test_common/keyboard_report_util.cpp

@@ -0,0 +1,76 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "keyboard_report_util.h"
+ #include <vector>
+ #include <algorithm>
+ using namespace testing;
+
+ namespace
+ {
+     std::vector<uint8_t> get_keys(const report_keyboard_t& report) {
+        std::vector<uint8_t> result;
+        #if defined(NKRO_ENABLE)
+        #error NKRO support not implemented yet
+        #elif defined(USB_6KRO_ENABLE)
+        #error 6KRO support not implemented yet
+        #else
+        for(size_t i=0; i<KEYBOARD_REPORT_KEYS; i++) {
+            if (report.keys[i]) {
+                result.emplace_back(report.keys[i]);
+            }
+        }
+        #endif
+        std::sort(result.begin(), result.end());
+        return result;
+     }
+ }
+
+bool operator==(const report_keyboard_t& lhs, const report_keyboard_t& rhs) {
+    auto lhskeys = get_keys(lhs);
+    auto rhskeys = get_keys(rhs);
+    return lhs.mods == rhs.mods && lhskeys == rhskeys;
+}
+
+std::ostream& operator<<(std::ostream& stream, const report_keyboard_t& value) {
+    stream << "Keyboard report:" << std::endl;
+    stream << "Mods: " << value.mods << std::endl;
+    // TODO: This should probably print friendly names for the keys
+    for (uint32_t k: get_keys(value)) {
+        stream << k << std::endl;
+    }
+    return stream;
+}
+
+KeyboardReportMatcher::KeyboardReportMatcher(const std::vector<uint8_t>& keys) {
+    // TODO: Support modifiers
+    memset(m_report.raw, 0, sizeof(m_report.raw));
+    for (auto k: keys) {
+        add_key_to_report(&m_report, k);
+    }
+}
+
+bool KeyboardReportMatcher::MatchAndExplain(report_keyboard_t& report, MatchResultListener* listener) const {
+    return m_report == report;
+}
+
+void KeyboardReportMatcher::DescribeTo(::std::ostream* os) const {
+    *os << "is equal to " << m_report;
+}
+
+void KeyboardReportMatcher::DescribeNegationTo(::std::ostream* os) const {
+    *os << "is not equal to " << m_report;
+}

+ 39 - 0
tests/test_common/keyboard_report_util.h

@@ -0,0 +1,39 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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/>.
+ */
+
+#pragma once
+#include "report.h"
+#include <ostream>
+#include "gmock/gmock.h"
+
+bool operator==(const report_keyboard_t& lhs, const report_keyboard_t& rhs);
+std::ostream& operator<<(std::ostream& stream, const report_keyboard_t& value);
+
+class KeyboardReportMatcher : public testing::MatcherInterface<report_keyboard_t&> {
+ public:
+    KeyboardReportMatcher(const std::vector<uint8_t>& keys);
+    virtual bool MatchAndExplain(report_keyboard_t& report, testing::MatchResultListener* listener) const override;
+    virtual void DescribeTo(::std::ostream* os) const override;
+    virtual void DescribeNegationTo(::std::ostream* os) const override;
+private:
+    report_keyboard_t m_report;
+};
+
+
+template<typename... Ts>
+inline testing::Matcher<report_keyboard_t&> KeyboardReport(Ts... keys) {
+    return testing::MakeMatcher(new KeyboardReportMatcher(std::vector<uint8_t>({keys...})));
+}

+ 60 - 0
tests/test_common/matrix.c

@@ -0,0 +1,60 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "matrix.h"
+#include "test_matrix.h"
+#include <string.h>
+
+static matrix_row_t matrix[MATRIX_ROWS] = {};
+
+void matrix_init(void) {
+    clear_all_keys();
+    matrix_init_quantum();
+}
+
+uint8_t matrix_scan(void) {
+    matrix_scan_quantum();
+    return 1;
+}
+
+matrix_row_t matrix_get_row(uint8_t row) {
+    return matrix[row];
+}
+
+void matrix_print(void) {
+
+}
+
+void matrix_init_kb(void) {
+
+}
+
+void matrix_scan_kb(void) {
+
+}
+
+void press_key(uint8_t col, uint8_t row) {
+    matrix[row] |= 1 << col;
+}
+
+void release_key(uint8_t col, uint8_t row) {
+    matrix[row] &= ~(1 << col);
+}
+
+void clear_all_keys(void) {
+    memset(matrix, 0, sizeof(matrix));
+}

+ 57 - 0
tests/test_common/test_driver.cpp

@@ -0,0 +1,57 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "test_driver.h"
+
+TestDriver* TestDriver::m_this = nullptr;
+
+TestDriver::TestDriver()
+    : m_driver{
+        &TestDriver::keyboard_leds,
+        &TestDriver::send_keyboard,
+        &TestDriver::send_mouse,
+        &TestDriver::send_system,
+        &TestDriver::send_consumer
+    }
+{
+    host_set_driver(&m_driver);
+    m_this = this;
+}
+
+TestDriver::~TestDriver() {
+    m_this = nullptr;
+}
+
+uint8_t TestDriver::keyboard_leds(void) {
+    return m_this->m_leds;
+}
+
+void TestDriver::send_keyboard(report_keyboard_t* report) {
+    m_this->send_keyboard_mock(*report);
+
+}
+
+void TestDriver::send_mouse(report_mouse_t* report) {
+    m_this->send_mouse_mock(*report);
+}
+
+void TestDriver::send_system(uint16_t data) {
+    m_this->send_system_mock(data);
+}
+
+void TestDriver::send_consumer(uint16_t data) {
+    m_this->send_consumer(data);
+}

+ 48 - 0
tests/test_common/test_driver.h

@@ -0,0 +1,48 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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/>.
+ */
+
+#ifndef TESTS_TEST_COMMON_TEST_DRIVER_H_
+#define TESTS_TEST_COMMON_TEST_DRIVER_H_
+
+#include "gmock/gmock.h"
+#include <stdint.h>
+#include "host.h"
+#include "keyboard_report_util.h"
+
+
+class TestDriver {
+public:
+    TestDriver();
+    ~TestDriver();
+    void set_leds(uint8_t leds) { m_leds = leds; }
+    
+    MOCK_METHOD1(send_keyboard_mock, void (report_keyboard_t&));
+    MOCK_METHOD1(send_mouse_mock, void (report_mouse_t&));
+    MOCK_METHOD1(send_system_mock, void (uint16_t));
+    MOCK_METHOD1(send_consumer_mock, void (uint16_t));
+private:
+    static uint8_t keyboard_leds(void);
+    static void send_keyboard(report_keyboard_t *report);
+    static void send_mouse(report_mouse_t* report);
+    static void send_system(uint16_t data);
+    static void send_consumer(uint16_t data);
+    host_driver_t m_driver;
+    uint8_t m_leds = 0;
+    static TestDriver* m_this;
+};
+
+
+#endif /* TESTS_TEST_COMMON_TEST_DRIVER_H_ */

+ 36 - 0
tests/test_common/test_fixture.cpp

@@ -0,0 +1,36 @@
+#include "test_fixture.h"
+#include "gmock/gmock.h"
+#include "test_driver.h"
+#include "test_matrix.h"
+#include "keyboard.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::Return;
+using testing::Between;
+
+void TestFixture::SetUpTestCase() {
+    TestDriver driver;
+    EXPECT_CALL(driver, send_keyboard_mock(_));
+    keyboard_init();
+}
+
+void TestFixture::TearDownTestCase() {
+}
+
+TestFixture::TestFixture() {
+}
+
+TestFixture::~TestFixture() {
+    TestDriver driver;
+    clear_all_keys();
+    // Run for a while to make sure all keys are completely released
+    // Should probably wait until tapping term etc, has timed out
+    EXPECT_CALL(driver, send_keyboard_mock(_)).Times(AnyNumber());
+    for (int i=0; i<100; i++) {
+        keyboard_task();
+    }
+    testing::Mock::VerifyAndClearExpectations(&driver); 
+    // Verify that the matrix really is cleared
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(Between(0, 1));
+}

+ 28 - 0
tests/test_common/test_fixture.h

@@ -0,0 +1,28 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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/>.
+ */
+
+ #pragma once
+
+#include "gtest/gtest.h"
+
+class TestFixture : public testing::Test {
+public:
+    TestFixture();
+    ~TestFixture();
+    static void SetUpTestCase();
+    static void TearDownTestCase();
+
+};

+ 32 - 0
tests/test_common/test_matrix.h

@@ -0,0 +1,32 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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/>.
+ */
+
+#ifndef TESTS_TEST_COMMON_TEST_MATRIX_H_
+#define TESTS_TEST_COMMON_TEST_MATRIX_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void press_key(uint8_t col, uint8_t row);
+void release_key(uint8_t col, uint8_t row);
+void clear_all_keys(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TESTS_TEST_COMMON_TEST_MATRIX_H_ */

+ 7 - 0
tmk_core/common.mk

@@ -3,6 +3,8 @@ ifeq ($(PLATFORM),AVR)
 	PLATFORM_COMMON_DIR = $(COMMON_DIR)/avr
 else ifeq ($(PLATFORM),CHIBIOS)
 	PLATFORM_COMMON_DIR = $(COMMON_DIR)/chibios
+else
+	PLATFORM_COMMON_DIR = $(COMMON_DIR)/test
 endif
 
 TMK_COMMON_SRC +=	$(COMMON_DIR)/host.c \
@@ -16,6 +18,7 @@ TMK_COMMON_SRC +=	$(COMMON_DIR)/host.c \
 	$(COMMON_DIR)/debug.c \
 	$(COMMON_DIR)/util.c \
 	$(COMMON_DIR)/eeconfig.c \
+	$(COMMON_DIR)/report.c \
 	$(PLATFORM_COMMON_DIR)/suspend.c \
 	$(PLATFORM_COMMON_DIR)/timer.c \
 	$(PLATFORM_COMMON_DIR)/bootloader.c \
@@ -29,6 +32,10 @@ ifeq ($(PLATFORM),CHIBIOS)
 	TMK_COMMON_SRC += $(PLATFORM_COMMON_DIR)/eeprom.c
 endif
 
+ifeq ($(PLATFORM),TEST)
+	TMK_COMMON_SRC += $(PLATFORM_COMMON_DIR)/eeprom.c
+endif
+
 
 
 # Option modules

+ 5 - 199
tmk_core/common/action_util.c

@@ -25,13 +25,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 extern keymap_config_t keymap_config;
 
 
-static inline void add_key_byte(uint8_t code);
-static inline void del_key_byte(uint8_t code);
-#ifdef NKRO_ENABLE
-static inline void add_key_bit(uint8_t code);
-static inline void del_key_bit(uint8_t code);
-#endif
-
 static uint8_t real_mods = 0;
 static uint8_t weak_mods = 0;
 static uint8_t macro_mods = 0;
@@ -50,6 +43,10 @@ static int8_t cb_count = 0;
 //report_keyboard_t keyboard_report = {};
 report_keyboard_t *keyboard_report = &(report_keyboard_t){};
 
+extern inline void add_key(uint8_t key);
+extern inline void del_key(uint8_t key);
+extern inline void clear_keys(void);
+
 #ifndef NO_ACTION_ONESHOT
 static int8_t oneshot_mods = 0;
 static int8_t oneshot_locked_mods = 0;
@@ -134,7 +131,7 @@ void send_keyboard_report(void) {
         }
 #endif
         keyboard_report->mods |= oneshot_mods;
-        if (has_anykey()) {
+        if (has_anykey(keyboard_report)) {
             clear_oneshot_mods();
         }
     }
@@ -143,38 +140,6 @@ void send_keyboard_report(void) {
     host_keyboard_send(keyboard_report);
 }
 
-/* key */
-void add_key(uint8_t key)
-{
-#ifdef NKRO_ENABLE
-    if (keyboard_protocol && keymap_config.nkro) {
-        add_key_bit(key);
-        return;
-    }
-#endif
-    add_key_byte(key);
-}
-
-void del_key(uint8_t key)
-{
-#ifdef NKRO_ENABLE
-    if (keyboard_protocol && keymap_config.nkro) {
-        del_key_bit(key);
-        return;
-    }
-#endif
-    del_key_byte(key);
-}
-
-void clear_keys(void)
-{
-    // not clear mods
-    for (int8_t i = 1; i < KEYBOARD_REPORT_SIZE; i++) {
-        keyboard_report->raw[i] = 0;
-    }
-}
-
-
 /* modifier */
 uint8_t get_mods(void) { return real_mods; }
 void add_mods(uint8_t mods) { real_mods |= mods; }
@@ -221,166 +186,7 @@ uint8_t get_oneshot_mods(void)
 /*
  * inspect keyboard state
  */
-uint8_t has_anykey(void)
-{
-    uint8_t cnt = 0;
-    for (uint8_t i = 1; i < KEYBOARD_REPORT_SIZE; i++) {
-        if (keyboard_report->raw[i])
-            cnt++;
-    }
-    return cnt;
-}
-
 uint8_t has_anymod(void)
 {
     return bitpop(real_mods);
 }
-
-uint8_t get_first_key(void)
-{
-#ifdef NKRO_ENABLE
-    if (keyboard_protocol && keymap_config.nkro) {
-        uint8_t i = 0;
-        for (; i < KEYBOARD_REPORT_BITS && !keyboard_report->nkro.bits[i]; i++)
-            ;
-        return i<<3 | biton(keyboard_report->nkro.bits[i]);
-    }
-#endif
-#ifdef USB_6KRO_ENABLE
-    uint8_t i = cb_head;
-    do {
-        if (keyboard_report->keys[i] != 0) {
-            break;
-        }
-        i = RO_INC(i);
-    } while (i != cb_tail);
-    return keyboard_report->keys[i];
-#else
-    return keyboard_report->keys[0];
-#endif
-}
-
-
-
-/* local functions */
-static inline void add_key_byte(uint8_t code)
-{
-#ifdef USB_6KRO_ENABLE
-    int8_t i = cb_head;
-    int8_t empty = -1;
-    if (cb_count) {
-        do {
-            if (keyboard_report->keys[i] == code) {
-                return;
-            }
-            if (empty == -1 && keyboard_report->keys[i] == 0) {
-                empty = i;
-            }
-            i = RO_INC(i);
-        } while (i != cb_tail);
-        if (i == cb_tail) {
-            if (cb_tail == cb_head) {
-                // buffer is full
-                if (empty == -1) {
-                    // pop head when has no empty space
-                    cb_head = RO_INC(cb_head);
-                    cb_count--;
-                }
-                else {
-                    // left shift when has empty space
-                    uint8_t offset = 1;
-                    i = RO_INC(empty);
-                    do {
-                        if (keyboard_report->keys[i] != 0) {
-                            keyboard_report->keys[empty] = keyboard_report->keys[i];
-                            keyboard_report->keys[i] = 0;
-                            empty = RO_INC(empty);
-                        }
-                        else {
-                            offset++;
-                        }
-                        i = RO_INC(i);
-                    } while (i != cb_tail);
-                    cb_tail = RO_SUB(cb_tail, offset);
-                }
-            }
-        }
-    }
-    // add to tail
-    keyboard_report->keys[cb_tail] = code;
-    cb_tail = RO_INC(cb_tail);
-    cb_count++;
-#else
-    int8_t i = 0;
-    int8_t empty = -1;
-    for (; i < KEYBOARD_REPORT_KEYS; i++) {
-        if (keyboard_report->keys[i] == code) {
-            break;
-        }
-        if (empty == -1 && keyboard_report->keys[i] == 0) {
-            empty = i;
-        }
-    }
-    if (i == KEYBOARD_REPORT_KEYS) {
-        if (empty != -1) {
-            keyboard_report->keys[empty] = code;
-        }
-    }
-#endif
-}
-
-static inline void del_key_byte(uint8_t code)
-{
-#ifdef USB_6KRO_ENABLE
-    uint8_t i = cb_head;
-    if (cb_count) {
-        do {
-            if (keyboard_report->keys[i] == code) {
-                keyboard_report->keys[i] = 0;
-                cb_count--;
-                if (cb_count == 0) {
-                    // reset head and tail
-                    cb_tail = cb_head = 0;
-                }
-                if (i == RO_DEC(cb_tail)) {
-                    // left shift when next to tail
-                    do {
-                        cb_tail = RO_DEC(cb_tail);
-                        if (keyboard_report->keys[RO_DEC(cb_tail)] != 0) {
-                            break;
-                        }
-                    } while (cb_tail != cb_head);
-                }
-                break;
-            }
-            i = RO_INC(i);
-        } while (i != cb_tail);
-    }
-#else
-    for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) {
-        if (keyboard_report->keys[i] == code) {
-            keyboard_report->keys[i] = 0;
-        }
-    }
-#endif
-}
-
-#ifdef NKRO_ENABLE
-static inline void add_key_bit(uint8_t code)
-{
-    if ((code>>3) < KEYBOARD_REPORT_BITS) {
-        keyboard_report->nkro.bits[code>>3] |= 1<<(code&7);
-    } else {
-        dprintf("add_key_bit: can't add: %02X\n", code);
-    }
-}
-
-static inline void del_key_bit(uint8_t code)
-{
-    if ((code>>3) < KEYBOARD_REPORT_BITS) {
-        keyboard_report->nkro.bits[code>>3] &= ~(1<<(code&7));
-    } else {
-        dprintf("del_key_bit: can't del: %02X\n", code);
-    }
-}
-#endif

+ 11 - 5
tmk_core/common/action_util.h

@@ -29,9 +29,17 @@ extern report_keyboard_t *keyboard_report;
 void send_keyboard_report(void);
 
 /* key */
-void add_key(uint8_t key);
-void del_key(uint8_t key);
-void clear_keys(void);
+inline void add_key(uint8_t key) {
+  add_key_to_report(keyboard_report, key);
+}
+
+inline void del_key(uint8_t key) {
+  del_key_from_report(keyboard_report, key);
+}
+
+inline void clear_keys(void) {
+  clear_keys_from_report(keyboard_report);
+}
 
 /* modifier */
 uint8_t get_mods(void);
@@ -82,9 +90,7 @@ uint8_t get_oneshot_layer_state(void);
 bool has_oneshot_layer_timed_out(void);
 
 /* inspect */
-uint8_t has_anykey(void);
 uint8_t has_anymod(void);
-uint8_t get_first_key(void);
 
 #ifdef __cplusplus
 }

+ 2 - 0
tmk_core/common/eeprom.h

@@ -4,6 +4,8 @@
 #if defined(__AVR__)
 #include <avr/eeprom.h>
 #else
+#include <stdint.h>
+
 uint8_t 	eeprom_read_byte (const uint8_t *__p);
 uint16_t 	eeprom_read_word (const uint16_t *__p);
 uint32_t 	eeprom_read_dword (const uint32_t *__p);

+ 1 - 1
tmk_core/common/progmem.h

@@ -3,7 +3,7 @@
 
 #if defined(__AVR__)
 #   include <avr/pgmspace.h>
-#elif defined(__arm__)
+#else
 #   define PROGMEM
 #   define pgm_read_byte(p)     *((unsigned char*)p)
 #   define pgm_read_word(p)     *((uint16_t*)p)

+ 207 - 0
tmk_core/common/report.c

@@ -0,0 +1,207 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "report.h"
+#include "host.h"
+#include "keycode_config.h"
+#include "debug.h"
+#include "util.h"
+
+uint8_t has_anykey(report_keyboard_t* keyboard_report)
+{
+    uint8_t cnt = 0;
+    for (uint8_t i = 1; i < KEYBOARD_REPORT_SIZE; i++) {
+        if (keyboard_report->raw[i])
+            cnt++;
+    }
+    return cnt;
+}
+
+uint8_t get_first_key(report_keyboard_t* keyboard_report)
+{
+#ifdef NKRO_ENABLE
+    if (keyboard_protocol && keymap_config.nkro) {
+        uint8_t i = 0;
+        for (; i < KEYBOARD_REPORT_BITS && !keyboard_report->nkro.bits[i]; i++)
+            ;
+        return i<<3 | biton(keyboard_report->nkro.bits[i]);
+    }
+#endif
+#ifdef USB_6KRO_ENABLE
+    uint8_t i = cb_head;
+    do {
+        if (keyboard_report->keys[i] != 0) {
+            break;
+        }
+        i = RO_INC(i);
+    } while (i != cb_tail);
+    return keyboard_report->keys[i];
+#else
+    return keyboard_report->keys[0];
+#endif
+}
+
+void add_key_byte(report_keyboard_t* keyboard_report, uint8_t code)
+{
+#ifdef USB_6KRO_ENABLE
+    int8_t i = cb_head;
+    int8_t empty = -1;
+    if (cb_count) {
+        do {
+            if (keyboard_report->keys[i] == code) {
+                return;
+            }
+            if (empty == -1 && keyboard_report->keys[i] == 0) {
+                empty = i;
+            }
+            i = RO_INC(i);
+        } while (i != cb_tail);
+        if (i == cb_tail) {
+            if (cb_tail == cb_head) {
+                // buffer is full
+                if (empty == -1) {
+                    // pop head when has no empty space
+                    cb_head = RO_INC(cb_head);
+                    cb_count--;
+                }
+                else {
+                    // left shift when has empty space
+                    uint8_t offset = 1;
+                    i = RO_INC(empty);
+                    do {
+                        if (keyboard_report->keys[i] != 0) {
+                            keyboard_report->keys[empty] = keyboard_report->keys[i];
+                            keyboard_report->keys[i] = 0;
+                            empty = RO_INC(empty);
+                        }
+                        else {
+                            offset++;
+                        }
+                        i = RO_INC(i);
+                    } while (i != cb_tail);
+                    cb_tail = RO_SUB(cb_tail, offset);
+                }
+            }
+        }
+    }
+    // add to tail
+    keyboard_report->keys[cb_tail] = code;
+    cb_tail = RO_INC(cb_tail);
+    cb_count++;
+#else
+    int8_t i = 0;
+    int8_t empty = -1;
+    for (; i < KEYBOARD_REPORT_KEYS; i++) {
+        if (keyboard_report->keys[i] == code) {
+            break;
+        }
+        if (empty == -1 && keyboard_report->keys[i] == 0) {
+            empty = i;
+        }
+    }
+    if (i == KEYBOARD_REPORT_KEYS) {
+        if (empty != -1) {
+            keyboard_report->keys[empty] = code;
+        }
+    }
+#endif
+}
+
+void del_key_byte(report_keyboard_t* keyboard_report, uint8_t code)
+{
+#ifdef USB_6KRO_ENABLE
+    uint8_t i = cb_head;
+    if (cb_count) {
+        do {
+            if (keyboard_report->keys[i] == code) {
+                keyboard_report->keys[i] = 0;
+                cb_count--;
+                if (cb_count == 0) {
+                    // reset head and tail
+                    cb_tail = cb_head = 0;
+                }
+                if (i == RO_DEC(cb_tail)) {
+                    // left shift when next to tail
+                    do {
+                        cb_tail = RO_DEC(cb_tail);
+                        if (keyboard_report->keys[RO_DEC(cb_tail)] != 0) {
+                            break;
+                        }
+                    } while (cb_tail != cb_head);
+                }
+                break;
+            }
+            i = RO_INC(i);
+        } while (i != cb_tail);
+    }
+#else
+    for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) {
+        if (keyboard_report->keys[i] == code) {
+            keyboard_report->keys[i] = 0;
+        }
+    }
+#endif
+}
+
+#ifdef NKRO_ENABLE
+void add_key_bit(report_keyboard_t* keyboard_report, uint8_t code)
+{
+    if ((code>>3) < KEYBOARD_REPORT_BITS) {
+        keyboard_report->nkro.bits[code>>3] |= 1<<(code&7);
+    } else {
+        dprintf("add_key_bit: can't add: %02X\n", code);
+    }
+}
+
+void del_key_bit(report_keyboard_t* keyboard_report, uint8_t code)
+{
+    if ((code>>3) < KEYBOARD_REPORT_BITS) {
+        keyboard_report->nkro.bits[code>>3] &= ~(1<<(code&7));
+    } else {
+        dprintf("del_key_bit: can't del: %02X\n", code);
+    }
+}
+#endif
+
+void add_key_to_report(report_keyboard_t* keyboard_report, int8_t key)
+{
+#ifdef NKRO_ENABLE
+    if (keyboard_protocol && keymap_config.nkro) {
+        add_key_bit(keyboard_report, key);
+        return;
+    }
+#endif
+    add_key_byte(keyboard_report, key);
+}
+
+void del_key_from_report(report_keyboard_t* keyboard_report, uint8_t key)
+{
+#ifdef NKRO_ENABLE
+    if (keyboard_protocol && keymap_config.nkro) {
+        del_key_bit(keyboard_report, key);
+        return;
+    }
+#endif
+    del_key_byte(keyboard_report, key);
+}
+
+void clear_keys_from_report(report_keyboard_t* keyboard_report)
+{
+    // not clear mods
+    for (int8_t i = 1; i < KEYBOARD_REPORT_SIZE; i++) {
+        keyboard_report->raw[i] = 0;
+    }
+}

+ 14 - 0
tmk_core/common/report.h

@@ -174,6 +174,20 @@ typedef struct {
     (key == KC_WWW_REFRESH      ?  AC_REFRESH : \
     (key == KC_WWW_FAVORITES    ?  AC_BOOKMARKS : 0)))))))))))))))))))))
 
+uint8_t has_anykey(report_keyboard_t* keyboard_report);
+uint8_t get_first_key(report_keyboard_t* keyboard_report);
+
+void add_key_byte(report_keyboard_t* keyboard_report, uint8_t code);
+void del_key_byte(report_keyboard_t* keyboard_report, uint8_t code);
+#ifdef NKRO_ENABLE
+void add_key_bit(report_keyboard_t* keyboard_report, uint8_t code);
+void del_key_bit(report_keyboard_t* keyboard_report, uint8_t code);
+#endif
+
+void add_key_to_report(report_keyboard_t* keyboard_report, int8_t key);
+void del_key_from_report(report_keyboard_t* keyboard_report, uint8_t key);
+void clear_keys_from_report(report_keyboard_t* keyboard_report);
+
 #ifdef __cplusplus
 }
 #endif

+ 19 - 0
tmk_core/common/test/bootloader.c

@@ -0,0 +1,19 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "bootloader.h"
+
+void bootloader_jump(void) {}

+ 98 - 0
tmk_core/common/test/eeprom.c

@@ -0,0 +1,98 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "eeprom.h"
+
+#define EEPROM_SIZE 32
+
+static uint8_t buffer[EEPROM_SIZE];
+
+uint8_t eeprom_read_byte(const uint8_t *addr) {
+	uintptr_t offset = (uintptr_t)addr;
+	return buffer[offset];
+}
+
+void eeprom_write_byte(uint8_t *addr, uint8_t value) {
+	uintptr_t offset = (uintptr_t)addr;
+	buffer[offset] = value;
+}
+
+uint16_t eeprom_read_word(const uint16_t *addr) {
+	const uint8_t *p = (const uint8_t *)addr;
+	return eeprom_read_byte(p) | (eeprom_read_byte(p+1) << 8);
+}
+
+uint32_t eeprom_read_dword(const uint32_t *addr) {
+	const uint8_t *p = (const uint8_t *)addr;
+	return eeprom_read_byte(p) | (eeprom_read_byte(p+1) << 8)
+		| (eeprom_read_byte(p+2) << 16) | (eeprom_read_byte(p+3) << 24);
+}
+
+void eeprom_read_block(void *buf, const void *addr, uint32_t len) {
+	const uint8_t *p = (const uint8_t *)addr;
+	uint8_t *dest = (uint8_t *)buf;
+	while (len--) {
+		*dest++ = eeprom_read_byte(p++);
+	}
+}
+
+void eeprom_write_word(uint16_t *addr, uint16_t value) {
+	uint8_t *p = (uint8_t *)addr;
+	eeprom_write_byte(p++, value);
+	eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_write_dword(uint32_t *addr, uint32_t value) {
+	uint8_t *p = (uint8_t *)addr;
+	eeprom_write_byte(p++, value);
+	eeprom_write_byte(p++, value >> 8);
+	eeprom_write_byte(p++, value >> 16);
+	eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_write_block(const void *buf, void *addr, uint32_t len) {
+	uint8_t *p = (uint8_t *)addr;
+	const uint8_t *src = (const uint8_t *)buf;
+	while (len--) {
+		eeprom_write_byte(p++, *src++);
+	}
+}
+
+void eeprom_update_byte(uint8_t *addr, uint8_t value) {
+	eeprom_write_byte(addr, value);
+}
+
+void eeprom_update_word(uint16_t *addr, uint16_t value) {
+	uint8_t *p = (uint8_t *)addr;
+	eeprom_write_byte(p++, value);
+	eeprom_write_byte(p, value >> 8);
+}
+
+void eeprom_update_dword(uint32_t *addr, uint32_t value) {
+	uint8_t *p = (uint8_t *)addr;
+	eeprom_write_byte(p++, value);
+	eeprom_write_byte(p++, value >> 8);
+	eeprom_write_byte(p++, value >> 16);
+	eeprom_write_byte(p, value >> 24);
+}
+
+void eeprom_update_block(const void *buf, void *addr, uint32_t len) {
+	uint8_t *p = (uint8_t *)addr;
+	const uint8_t *src = (const uint8_t *)buf;
+	while (len--) {
+		eeprom_write_byte(p++, *src++);
+	}
+}

+ 17 - 0
tmk_core/common/test/suspend.c

@@ -0,0 +1,17 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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/>.
+ */
+
+

+ 30 - 0
tmk_core/common/test/timer.c

@@ -0,0 +1,30 @@
+/* Copyright 2017 Fred Sundvik
+ *
+ * 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 "timer.h"
+
+// TODO: the timer should work, but at a much faster rate than realtime
+// It should also have some kind of integration with the testing system
+
+void timer_init(void) {}
+
+void timer_clear(void) {}
+
+uint16_t timer_read(void) { return 0; }
+uint32_t timer_read32(void) { return 0; }
+uint16_t timer_elapsed(uint16_t last) { return 0; }
+uint32_t timer_elapsed32(uint32_t last) { return 0; }
+

+ 6 - 3
tmk_core/common/wait.h

@@ -9,13 +9,16 @@ extern "C" {
 #   include <util/delay.h>
 #   define wait_ms(ms)  _delay_ms(ms)
 #   define wait_us(us)  _delay_us(us)
-#elif defined(PROTOCOL_CHIBIOS) /* __AVR__ */
+#elif defined(PROTOCOL_CHIBIOS)
 #   include "ch.h"
 #   define wait_ms(ms) chThdSleepMilliseconds(ms)
 #   define wait_us(us) chThdSleepMicroseconds(us)
-#elif defined(__arm__) /* __AVR__ */
+#elif defined(__arm__)
 #   include "wait_api.h"
-#endif /* __AVR__ */
+#else  // Unit tests
+#define wait_ms(ms)
+#define wait_us(us)
+#endif
 
 #ifdef __cplusplus
 }