Diff
1diff --git a/bin/qmk b/bin/qmk
2index 3c00a675eb16b4ab652009073470c9f80c0e7773..1aa16e17dcccf512390d75880f7d9a07e093b45a 100755
3--- a/bin/qmk
4+++ b/bin/qmk
5@@ -94,4 +94,10 @@ else:
6 exit(1)
7
8 if __name__ == '__main__':
9- milc.cli()
10+ return_code = milc.cli()
11+ if return_code is False:
12+ exit(1)
13+ elif return_code is not True and isinstance(return_code, int) and return_code < 256:
14+ exit(return_code)
15+ else:
16+ exit(0)
17diff --git a/keyboards/handwired/onekey/pytest/config.h b/keyboards/handwired/onekey/pytest/config.h
18new file mode 100644
19index 0000000000000000000000000000000000000000..f6bedcfe64812b0330624c00049d7abcf67c8925
20--- /dev/null
21+++ b/keyboards/handwired/onekey/pytest/config.h
22@@ -0,0 +1,23 @@
23+/* Copyright 2019
24+ *
25+ * This program is free software: you can redistribute it and/or modify
26+ * it under the terms of the GNU General Public License as published by
27+ * the Free Software Foundation, either version 2 of the License, or
28+ * (at your option) any later version.
29+ *
30+ * This program is distributed in the hope that it will be useful,
31+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
32+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33+ * GNU General Public License for more details.
34+ *
35+ * You should have received a copy of the GNU General Public License
36+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
37+ */
38+
39+#pragma once
40+
41+#include "config_common.h"
42+
43+#define MATRIX_COL_PINS { A3 }
44+#define MATRIX_ROW_PINS { A2 }
45+#define UNUSED_PINS
46diff --git a/keyboards/handwired/onekey/pytest/readme.md b/keyboards/handwired/onekey/pytest/readme.md
47new file mode 100644
48index 0000000000000000000000000000000000000000..dbbd991f1c3778a3078f1ed6831e7273d8e5838f
49--- /dev/null
50+++ b/keyboards/handwired/onekey/pytest/readme.md
51@@ -0,0 +1,3 @@
52+# PyTest onekey
53+
54+This is used by the python test framework. It's probably not useful otherwise.
55diff --git a/keyboards/handwired/onekey/pytest/rules.mk b/keyboards/handwired/onekey/pytest/rules.mk
56new file mode 100644
57index 0000000000000000000000000000000000000000..b17a3d0316b57f873d2c36a5916e34ce2cb10f83
58--- /dev/null
59+++ b/keyboards/handwired/onekey/pytest/rules.mk
60@@ -0,0 +1,2 @@
61+# MCU name
62+MCU = STM32F303
63diff --git a/keyboards/handwired/onekey/pytest/templates/keymap.c b/keyboards/handwired/onekey/pytest/templates/keymap.c
64new file mode 100644
65index 0000000000000000000000000000000000000000..d355210c47f72b941219b50897a58d417a9984b3
66--- /dev/null
67+++ b/keyboards/handwired/onekey/pytest/templates/keymap.c
68@@ -0,0 +1 @@
69+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {__KEYMAP_GOES_HERE__};
70diff --git a/lib/python/milc.py b/lib/python/milc.py
71index 6e82edf8b1935b79bc6a79f996c9c32db2de9268..c62c1b166c4215fcf5e85fc80c36e74ad2384fa1 100644
72--- a/lib/python/milc.py
73+++ b/lib/python/milc.py
74@@ -534,8 +534,7 @@ class MILC(object):
75 if not self._inside_context_manager:
76 # If they didn't use the context manager use it ourselves
77 with self:
78- self.__call__()
79- return
80+ return self.__call__()
81
82 if not self._entrypoint:
83 raise RuntimeError('No entrypoint provided!')
84diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py
85index 9ce765a4b517d759832649719c86da0da62664f3..c5a144363c95a22865ad7e8ab3d6169971867736 100755
86--- a/lib/python/qmk/cli/doctor.py
87+++ b/lib/python/qmk/cli/doctor.py
88@@ -2,9 +2,11 @@
89
90 Check up for QMK environment.
91 """
92-import shutil
93-import platform
94 import os
95+import platform
96+import shutil
97+import subprocess
98+from glob import glob
99
100 from milc import cli
101
102@@ -16,32 +18,44 @@ def main(cli):
103 This is currently very simple, it just checks that all the expected binaries are on your system.
104
105 TODO(unclaimed):
106- * [ ] Run the binaries to make sure they work
107 * [ ] Compile a trivial program with each compiler
108 * [ ] Check for udev entries on linux
109 """
110
111 binaries = ['dfu-programmer', 'avrdude', 'dfu-util', 'avr-gcc', 'arm-none-eabi-gcc']
112+ binaries += glob('bin/qmk-*')
113
114- cli.log.info('QMK Doctor is Checking your environment')
115+ cli.log.info('QMK Doctor is checking your environment')
116
117 ok = True
118 for binary in binaries:
119 res = shutil.which(binary)
120 if res is None:
121- cli.log.error('{fg_red}QMK can\'t find ' + binary + ' in your path')
122+ cli.log.error("{fg_red}QMK can't find %s in your path", binary)
123 ok = False
124+ else:
125+ try:
126+ subprocess.run([binary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5, check=True)
127+ except subprocess.CalledProcessError:
128+ cli.log.error("{fg_red}Can't run `%s --version`", binary)
129+ ok = False
130
131 OS = platform.system()
132 if OS == "Darwin":
133 cli.log.info("Detected {fg_cyan}macOS")
134 elif OS == "Linux":
135 cli.log.info("Detected {fg_cyan}linux")
136- test = 'systemctl list-unit-files | grep enabled | grep -i ModemManager'
137- if os.system(test) == 0:
138- cli.log.warn("{bg_yellow}Detected modem manager. Please disable it if you are using Pro Micros")
139+ if shutil.which('systemctl'):
140+ test = 'systemctl list-unit-files | grep enabled | grep -i ModemManager'
141+ if os.system(test) == 0:
142+ cli.log.warn("{bg_yellow}Detected modem manager. Please disable it if you are using Pro Micros")
143+ else:
144+ cli.log.warn("Can't find systemctl to check for ModemManager.")
145 else:
146 cli.log.info("Assuming {fg_cyan}Windows")
147
148 if ok:
149 cli.log.info('{fg_green}QMK is ready to go')
150+ else:
151+ cli.log.info('{fg_yellow}Problems detected, please fix these problems before proceeding.')
152+ # FIXME(skullydazed): Link to a document about troubleshooting, or discord or something
153diff --git a/lib/python/qmk/cli/nose2.py b/lib/python/qmk/cli/nose2.py
154new file mode 100644
155index 0000000000000000000000000000000000000000..c6c9c67b30a540b17652d1af621526a223b94e3c
156--- /dev/null
157+++ b/lib/python/qmk/cli/nose2.py
158@@ -0,0 +1,18 @@
159+"""QMK Python Unit Tests
160+
161+QMK script to run unit and integration tests against our python code.
162+"""
163+from milc import cli
164+
165+
166+@cli.entrypoint('QMK Python Unit Tests')
167+def main(cli):
168+ """Use nose2 to run unittests
169+ """
170+ try:
171+ import nose2
172+ except ImportError:
173+ cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2')
174+ return False
175+
176+ nose2.discover()
177diff --git a/lib/python/qmk/tests/__init__.py b/lib/python/qmk/tests/__init__.py
178new file mode 100644
179index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
180--- /dev/null
181+++ b/lib/python/qmk/tests/__init__.py
182diff --git a/lib/python/qmk/tests/attrdict.py b/lib/python/qmk/tests/attrdict.py
183new file mode 100644
184index 0000000000000000000000000000000000000000..a2584b923337be0f9455e70ab0cf9d731abb6f17
185--- /dev/null
186+++ b/lib/python/qmk/tests/attrdict.py
187@@ -0,0 +1,8 @@
188+class AttrDict(dict):
189+ """A dictionary that can be accessed by attributes.
190+
191+ This should only be used to mock objects for unit testing. Please do not use this outside of qmk.tests.
192+ """
193+ def __init__(self, *args, **kwargs):
194+ super(AttrDict, self).__init__(*args, **kwargs)
195+ self.__dict__ = self
196diff --git a/lib/python/qmk/tests/onekey_export.json b/lib/python/qmk/tests/onekey_export.json
197new file mode 100644
198index 0000000000000000000000000000000000000000..95f0a980fe6338498842b5885cb99504f5b18a7a
199--- /dev/null
200+++ b/lib/python/qmk/tests/onekey_export.json
201@@ -0,0 +1,6 @@
202+{
203+ "keyboard":"handwired/onekey/pytest",
204+ "keymap":"pytest_unittest",
205+ "layout":"LAYOUT",
206+ "layers":[["KC_A"]]
207+}
208diff --git a/lib/python/qmk/tests/test_qmk_errors.py b/lib/python/qmk/tests/test_qmk_errors.py
209new file mode 100644
210index 0000000000000000000000000000000000000000..3f6b567137c14b4879a2bfd107aba9c3ea40580d
211--- /dev/null
212+++ b/lib/python/qmk/tests/test_qmk_errors.py
213@@ -0,0 +1,7 @@
214+from qmk.errors import NoSuchKeyboardError
215+
216+def test_NoSuchKeyboardError():
217+ try:
218+ raise(NoSuchKeyboardError("test message"))
219+ except NoSuchKeyboardError as e:
220+ assert e.message == 'test message'
221diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py
222new file mode 100644
223index 0000000000000000000000000000000000000000..6a565ee900c1d3b5ddf147c19b7ed514ad40d400
224--- /dev/null
225+++ b/lib/python/qmk/tests/test_qmk_keymap.py
226@@ -0,0 +1,18 @@
227+import qmk.keymap
228+
229+def test_template_onekey_proton_c():
230+ templ = qmk.keymap.template('handwired/onekey/proton_c')
231+ assert templ == qmk.keymap.DEFAULT_KEYMAP_C
232+
233+
234+def test_template_onekey_pytest():
235+ templ = qmk.keymap.template('handwired/onekey/pytest')
236+ assert templ == 'const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {__KEYMAP_GOES_HERE__};\n'
237+
238+
239+def test_generate_onekey_pytest():
240+ templ = qmk.keymap.generate('handwired/onekey/pytest', 'LAYOUT', [['KC_A']])
241+ assert templ == 'const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [0] = LAYOUT(KC_A)};\n'
242+
243+
244+# FIXME(skullydazed): Add a test for qmk.keymap.write that mocks up an FD.
245diff --git a/lib/python/qmk/tests/test_qmk_path.py b/lib/python/qmk/tests/test_qmk_path.py
246new file mode 100644
247index 0000000000000000000000000000000000000000..23816be7ede28cf9b4b6efdf700f096a5d4548d9
248--- /dev/null
249+++ b/lib/python/qmk/tests/test_qmk_path.py
250@@ -0,0 +1,12 @@
251+import os
252+
253+import qmk.path
254+
255+def test_keymap_onekey_pytest():
256+ path = qmk.path.keymap('handwired/onekey/pytest')
257+ assert path == 'keyboards/handwired/onekey/keymaps'
258+
259+
260+def test_normpath():
261+ path = qmk.path.normpath('lib/python')
262+ assert path == os.environ['ORIG_CWD'] + '/lib/python'
263diff --git a/nose2.cfg b/nose2.cfg
264new file mode 100644
265index 0000000000000000000000000000000000000000..136354553bd63fab740f7b121c21e8f3852967bc
266--- /dev/null
267+++ b/nose2.cfg
268@@ -0,0 +1,2 @@
269+[unittest]
270+start-dir = lib/python/qmk/tests