diff --git a/keymapper/dev/macros.py b/keymapper/dev/macros.py index 87fae3b4..80e6f6e7 100644 --- a/keymapper/dev/macros.py +++ b/keymapper/dev/macros.py @@ -53,7 +53,6 @@ DEBUG = 6 def is_this_a_macro(output): """Figure out if this is a macro.""" - # TODO test if not isinstance(output, str): return False @@ -86,7 +85,6 @@ class _Macro: # all required capabilities, without those of child macros self.capabilities = set() - # TODO test that child_macros is properly populated self.child_macros = [] def get_capabilities(self): @@ -122,7 +120,7 @@ class _Macro: self.running = False def press_key(self): - """Tell all child macros that the key was pressed down.""" + """Tell this and all child macros that the key was pressed down.""" self.holding = True for macro in self.child_macros: macro.press_key() @@ -345,9 +343,11 @@ def _parse_recurse(macro, macro_instance=None, depth=0): call_match = re.match(r'^(\w+)\(', macro) call = call_match[1] if call_match else None if call is not None: - if 'k(' not in macro: - # TODO test - raise Exception(f'"{macro}" doesn\'t write any keys') + if 'k(' not in macro and 'm(' not in macro: + # maybe this just applies a modifier for a certain amout of time. + # and maybe it's a wait in repeat or something. Don't make it + # fail here. + logger.warn(f'"{macro}" doesn\'t write any keys (using k)') # available functions in the macro and the number of their # parameters @@ -422,7 +422,6 @@ def parse(macro, return_errors=False): return_errors : bool if True, returns errors as a string or None if parsing worked """ - # TODO test return_errors # whitespaces, tabs, newlines and such don't serve a purpose. make # the log output clearer and the parsing easier. macro = re.sub(r'\s', '', macro) diff --git a/tests/testcases/test_macros.py b/tests/testcases/test_macros.py index f2bc195f..aca6680a 100644 --- a/tests/testcases/test_macros.py +++ b/tests/testcases/test_macros.py @@ -23,7 +23,8 @@ import time import unittest import asyncio -from keymapper.dev.macros import parse, _Macro, _extract_params +from keymapper.dev.macros import parse, _Macro, _extract_params, \ + is_this_a_macro from keymapper.config import config from keymapper.state import system_mapping @@ -40,6 +41,17 @@ class TestMacros(unittest.TestCase): """Where macros should write codes to.""" self.result.append((code, value)) + def test_is_this_a_macro(self): + self.assertTrue(is_this_a_macro('k(1)')) + self.assertTrue(is_this_a_macro('k(1).k(2)')) + self.assertTrue(is_this_a_macro('r(1, k(1).k(2))')) + + self.assertFalse(is_this_a_macro('1')) + self.assertFalse(is_this_a_macro('key_kp1')) + self.assertFalse(is_this_a_macro('btn_left')) + self.assertFalse(is_this_a_macro('minus')) + self.assertFalse(is_this_a_macro('k')) + def test_extract_params(self): def expect(raw, expectation): self.assertListEqual(_extract_params(raw), expectation) @@ -88,6 +100,7 @@ class TestMacros(unittest.TestCase): self.loop.run_until_complete(macro.run()) self.assertListEqual(self.result, [(one_code, 1), (one_code, 0)]) + self.assertEqual(len(macro.child_macros), 0) def test_1(self): macro = parse('k(1).k(a).k(3)') @@ -104,12 +117,70 @@ class TestMacros(unittest.TestCase): (system_mapping.get('a'), 1), (system_mapping.get('a'), 0), (system_mapping.get('3'), 1), (system_mapping.get('3'), 0), ]) - + self.assertEqual(len(macro.child_macros), 0) + + def test_return_errors(self): + error = parse('k(1).h(k(a)).k(3)', return_errors=True) + self.assertIsNone(error) + error = parse('k(1))', return_errors=True) + self.assertIn('bracket', error) + error = parse('k((1)', return_errors=True) + self.assertIn('bracket', error) + error = parse('k((1).k)', return_errors=True) + self.assertIsNotNone(error) + error = parse('k()', return_errors=True) + self.assertIsNotNone(error) + error = parse('r(a, k(1))', return_errors=True) + self.assertIsNotNone(error) + error = parse('k(1, 1)', return_errors=True) + self.assertIsNotNone(error) + error = parse('h(1, 1)', return_errors=True) + self.assertIsNotNone(error) + error = parse('h(h(h(1, 1)))', return_errors=True) + self.assertIsNotNone(error) + error = parse('r(1)', return_errors=True) + self.assertIsNotNone(error) + error = parse('r(1, 1)', return_errors=True) + self.assertIsNotNone(error) + error = parse('r(k(1), 1)', return_errors=True) + self.assertIsNotNone(error) + error = parse('r(1, k(1))', return_errors=True) + self.assertIsNone(error) + + def test_hold(self): + macro = parse('k(1).h(k(a)).k(3)') + macro.set_handler(self.handler) + self.assertSetEqual(macro.get_capabilities(), { + system_mapping.get('1'), + system_mapping.get('a'), + system_mapping.get('3') + }) + + macro.press_key() + asyncio.ensure_future(macro.run()) + self.loop.run_until_complete(asyncio.sleep(0.2)) + macro.release_key() + self.loop.run_until_complete(asyncio.sleep(0.05)) + + self.assertEqual( + self.result[0], + (system_mapping.get('1'), 1) + ) + self.assertEqual( + self.result[-1], + (system_mapping.get('3'), 0) + ) + + code_a = system_mapping.get('a') + self.assertGreater(self.result.count((code_a, 1)), 2) + + self.assertEqual(len(macro.child_macros), 1) + def test_2(self): start = time.time() repeats = 20 - macro = parse(f'r({repeats}, k(k))') + macro = parse(f'r({repeats}, k(k)).r(1, k(k))') macro.set_handler(self.handler) k_code = system_mapping.get('k') self.assertSetEqual(macro.get_capabilities(), {k_code}) @@ -119,7 +190,14 @@ class TestMacros(unittest.TestCase): sleep_time = 2 * repeats * keystroke_sleep / 1000 self.assertGreater(time.time() - start, sleep_time * 0.9) self.assertLess(time.time() - start, sleep_time * 1.1) - self.assertListEqual(self.result, [(k_code, 1), (k_code, 0)] * repeats) + + self.assertListEqual( + self.result, + [(k_code, 1), (k_code, 0)] * (repeats + 1) + ) + + self.assertEqual(len(macro.child_macros), 2) + self.assertEqual(len(macro.child_macros[0].child_macros), 0) def test_3(self): start = time.time() @@ -140,6 +218,8 @@ class TestMacros(unittest.TestCase): (m_code, 1), (m_code, 0), (m_code, 1), (m_code, 0), ]) + self.assertEqual(len(macro.child_macros), 1) + self.assertEqual(len(macro.child_macros[0].child_macros), 0) def test_4(self): macro = parse(' r(2,\nk(\nr ).k(minus\n )).k(m) ') @@ -159,12 +239,17 @@ class TestMacros(unittest.TestCase): (minus, 1), (minus, 0), (m, 1), (m, 0), ]) + self.assertEqual(len(macro.child_macros), 1) + self.assertEqual(len(macro.child_macros[0].child_macros), 0) def test_5(self): start = time.time() macro = parse('w(200).r(2,m(w,\nr(2,\tk(BtN_LeFt))).w(10).k(k))') macro.set_handler(self.handler) + self.assertEqual(len(macro.child_macros), 1) + self.assertEqual(len(macro.child_macros[0].child_macros), 1) + w = system_mapping.get('w') left = system_mapping.get('bTn_lEfT') k = system_mapping.get('k')