From 9b9a6593a7160527dd724eb8d28bee69cb16f843 Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Mon, 7 Oct 2024 08:40:44 +1300 Subject: [PATCH 01/10] Added optional wait variance Added an optional argument to wait to the amount of time wait can vary between between the 2 specific values. 2nd 'vary' argument is ignored if value is less than 1st 'time' argument. This more closely resembles the wiki's description of wait as having a range value. --- inputremapper/injection/macros/macro.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index 88e5c4a39..df539e165 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -40,6 +40,7 @@ import copy import math import re +import random from typing import List, Callable, Awaitable, Tuple, Optional, Union, Any from evdev.ecodes import ( @@ -540,12 +541,18 @@ async def task(handler: Callable): self.tasks.append(task) - def add_wait(self, time: Union[int, float]): + def add_wait(self, time: Union[str, int], vary: int=0): """Wait time in milliseconds.""" - time = _type_check(time, [int, float], "wait", 1) + time = _type_check(time, [int], "wait", 1) + vary = _type_check(vary, [int], "wait", 2) async def task(_): - await asyncio.sleep(_resolve(time, [int, float]) / 1000) + if _resolve(vary, [int]) > _resolve(time, [int]): + time_vary = random.randint(_resolve(time, [int]), _resolve(vary, [int])) + else: + time_vary = _resolve(time, [int]) + + await asyncio.sleep(_resolve(time_vary, [int, float]) / 1000) self.tasks.append(task) From 59d91b68b367f8ab2a0afb4f1a4b93e84fc1139a Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Mon, 7 Oct 2024 18:56:27 +1300 Subject: [PATCH 02/10] Removed _resolves & renamed varible Removed redundant _resolve calls in task and renamed `vary` variable to max_time so it makes more sense. --- inputremapper/injection/macros/macro.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index df539e165..bcf09b35b 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -541,18 +541,21 @@ async def task(handler: Callable): self.tasks.append(task) - def add_wait(self, time: Union[str, int], vary: int=0): + def add_wait(self, time: Union[str, int], max_time: int=0): """Wait time in milliseconds.""" time = _type_check(time, [int], "wait", 1) - vary = _type_check(vary, [int], "wait", 2) + max_time = _type_check(max_time, [int], "wait", 2) + + time = _resolve(time, [int]) + max_time = _resolve(max_time, [int]) async def task(_): - if _resolve(vary, [int]) > _resolve(time, [int]): - time_vary = random.randint(_resolve(time, [int]), _resolve(vary, [int])) + if max_time > time: + time_vary = random.randint(time, max_time) else: - time_vary = _resolve(time, [int]) + time_vary = time - await asyncio.sleep(_resolve(time_vary, [int, float]) / 1000) + await asyncio.sleep(time_vary / 1000) self.tasks.append(task) From 7126912735ed23023357c048ccff25203538152b Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Tue, 8 Oct 2024 11:58:30 +1300 Subject: [PATCH 03/10] moved resolver into task moved resolver into task so that changes in incoming values are respected --- inputremapper/injection/macros/macro.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index bcf09b35b..449053346 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -546,16 +546,16 @@ def add_wait(self, time: Union[str, int], max_time: int=0): time = _type_check(time, [int], "wait", 1) max_time = _type_check(max_time, [int], "wait", 2) - time = _resolve(time, [int]) - max_time = _resolve(max_time, [int]) - async def task(_): - if max_time > time: - time_vary = random.randint(time, max_time) + resolved_min_time = _resolve(time, [int]) + resolved_max_time = _resolve(max_time, [int]) + + if resolved_max_time > resolved_min_time: + variabletime = random.randint(resolved_min_time, resolved_max_time) else: - time_vary = time + variabletime = resolved_min_time - await asyncio.sleep(time_vary / 1000) + await asyncio.sleep(variabletime / 1000) self.tasks.append(task) From cb38c902d09acd4aab8c145158e9d39177cfef80 Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Wed, 9 Oct 2024 10:16:49 +1300 Subject: [PATCH 04/10] Optional max_time defaults to None Shortcircuit optional argument comparison to ignore if not specified to be more consistent with other optional arguments. --- inputremapper/injection/macros/macro.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index 449053346..29e986eb2 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -541,7 +541,7 @@ async def task(handler: Callable): self.tasks.append(task) - def add_wait(self, time: Union[str, int], max_time: int=0): + def add_wait(self, time: Union[str, int], max_time=None): """Wait time in milliseconds.""" time = _type_check(time, [int], "wait", 1) max_time = _type_check(max_time, [int], "wait", 2) @@ -550,7 +550,7 @@ async def task(_): resolved_min_time = _resolve(time, [int]) resolved_max_time = _resolve(max_time, [int]) - if resolved_max_time > resolved_min_time: + if resolved_max_time is not None and resolved_max_time > resolved_min_time: variabletime = random.randint(resolved_min_time, resolved_max_time) else: variabletime = resolved_min_time From 74df2113c4568e9e525caef9e5b4e38a2ff26305 Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Wed, 9 Oct 2024 10:20:49 +1300 Subject: [PATCH 05/10] Forgot to check is max_time is int or None Forgot to check is max_time is int or None --- inputremapper/injection/macros/macro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index 29e986eb2..76ab73089 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -544,7 +544,7 @@ async def task(handler: Callable): def add_wait(self, time: Union[str, int], max_time=None): """Wait time in milliseconds.""" time = _type_check(time, [int], "wait", 1) - max_time = _type_check(max_time, [int], "wait", 2) + max_time = _type_check(max_time, [int,None], "wait", 2) async def task(_): resolved_min_time = _resolve(time, [int]) From 80f6c38a36f37e34438b6afa1cdb53ebebd9698d Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Fri, 11 Oct 2024 11:16:25 +1300 Subject: [PATCH 06/10] Added wait testing Added testing of `wait` function. --- tests/unit/test_macros.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unit/test_macros.py b/tests/unit/test_macros.py index 939ac6526..39d0f7bf1 100644 --- a/tests/unit/test_macros.py +++ b/tests/unit/test_macros.py @@ -915,6 +915,39 @@ async def test_6(self): self.assertIsInstance(macro, Macro) self.assertListEqual(self.result, []) + async def test_wait_1(self): + macro = parse("wait(10).key(1)", self.context, DummyMapping, True) + one_code = system_mapping.get("1") + + await macro.run(self.handler) + self.assertListEqual( + self.result, + [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)], + ) + self.assertEqual(len(macro.child_macros), 0) + + async def test_wait_2(self): + macro = parse("wait(10,100).key(1)", self.context, DummyMapping, True) + one_code = system_mapping.get("1") + + await macro.run(self.handler) + self.assertListEqual( + self.result, + [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)], + ) + self.assertEqual(len(macro.child_macros), 0) + + async def test_wait_3(self): + macro = parse("set(a,100).wait(10, $a).key(1)", self.context, DummyMapping, True) + one_code = system_mapping.get("1") + + await macro.run(self.handler) + self.assertListEqual( + self.result, + [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)], + ) + self.assertEqual(len(macro.child_macros), 0) + async def test_duplicate_run(self): # it won't restart the macro, because that may screw up the # internal state (in particular the _trigger_release_event). From 5dd9295a7f562aaecec74a6982bcd52bfad67abf Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Mon, 21 Oct 2024 20:05:24 +1300 Subject: [PATCH 07/10] crude test runtime Crude wait testing as we're rapidly reaching my limits in python vs the time I have for this. --- tests/unit/test_macros.py | 78 ++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/tests/unit/test_macros.py b/tests/unit/test_macros.py index 39d0f7bf1..929e8baac 100644 --- a/tests/unit/test_macros.py +++ b/tests/unit/test_macros.py @@ -919,34 +919,76 @@ async def test_wait_1(self): macro = parse("wait(10).key(1)", self.context, DummyMapping, True) one_code = system_mapping.get("1") - await macro.run(self.handler) - self.assertListEqual( - self.result, - [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)], - ) - self.assertEqual(len(macro.child_macros), 0) + minTime = 999999 + maxTime = 0 + its = 10 + totalStart = time.time() + for loopcount in range(its): + runStart = time.time() + await macro.run(self.handler) + runEnd = time.time() + runTime = runEnd - runStart + if runTime < minTime: + minTime = runTime + + if runTime > maxTime: + maxTime = runTime + + totalEnd = time.time() + totalTime = totalEnd - totalStart + meanTime = totalTime / its + print(f"Runtimes min, mean, max {(minTime*1000, meanTime*1000, maxTime*1000)}") + self.assertTrue(self.result, [totalTime, meanTime, minTime, maxTime]) async def test_wait_2(self): macro = parse("wait(10,100).key(1)", self.context, DummyMapping, True) one_code = system_mapping.get("1") - await macro.run(self.handler) - self.assertListEqual( - self.result, - [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)], - ) - self.assertEqual(len(macro.child_macros), 0) + minTime = 999999 + maxTime = 0 + its = 10 + totalStart = time.time() + for loopcount in range(its): + runStart = time.time() + await macro.run(self.handler) + runEnd = time.time() + runTime = runEnd - runStart + if runTime < minTime: + minTime = runTime + + if runTime > maxTime: + maxTime = runTime + + totalEnd = time.time() + totalTime = totalEnd - totalStart + meanTime = totalTime / its + print(f"Runtimes min, mean, max {(minTime*1000, meanTime*1000, maxTime*1000)}") + self.assertTrue(self.result, [totalTime, meanTime, minTime, maxTime]) async def test_wait_3(self): macro = parse("set(a,100).wait(10, $a).key(1)", self.context, DummyMapping, True) one_code = system_mapping.get("1") - await macro.run(self.handler) - self.assertListEqual( - self.result, - [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)], - ) - self.assertEqual(len(macro.child_macros), 0) + minTime = 999999 + maxTime = 0 + its = 10 + totalStart = time.time() + for loopcount in range(its): + runStart = time.time() + await macro.run(self.handler) + runEnd = time.time() + runTime = runEnd - runStart + if runTime < minTime: + minTime = runTime + + if runTime > maxTime: + maxTime = runTime + + totalEnd = time.time() + totalTime = totalEnd - totalStart + meanTime = totalTime / its + print(f"Runtimes min, mean, max {(minTime*1000, meanTime*1000, maxTime*1000)}") + self.assertTrue(self.result, [totalTime, meanTime, minTime, maxTime]) async def test_duplicate_run(self): # it won't restart the macro, because that may screw up the From 1bec762ea28716e54c39c97bfb672af602d98512 Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Tue, 22 Oct 2024 10:35:31 +1300 Subject: [PATCH 08/10] unit testing wait macro --- tests/unit/test_macros.py | 107 ++++++++++++++------------------------ 1 file changed, 38 insertions(+), 69 deletions(-) diff --git a/tests/unit/test_macros.py b/tests/unit/test_macros.py index 929e8baac..13bd5c9f8 100644 --- a/tests/unit/test_macros.py +++ b/tests/unit/test_macros.py @@ -915,81 +915,50 @@ async def test_6(self): self.assertIsInstance(macro, Macro) self.assertListEqual(self.result, []) - async def test_wait_1(self): - macro = parse("wait(10).key(1)", self.context, DummyMapping, True) - one_code = system_mapping.get("1") + async def test_wait_1_core(self): + mapping = DummyMapping() + mapping.macro_key_sleep_ms = 0 + macro = parse("repeat(5, wait(50))", self.context, mapping, True) - minTime = 999999 - maxTime = 0 - its = 10 - totalStart = time.time() - for loopcount in range(its): - runStart = time.time() - await macro.run(self.handler) - runEnd = time.time() - runTime = runEnd - runStart - if runTime < minTime: - minTime = runTime - - if runTime > maxTime: - maxTime = runTime - - totalEnd = time.time() - totalTime = totalEnd - totalStart - meanTime = totalTime / its - print(f"Runtimes min, mean, max {(minTime*1000, meanTime*1000, maxTime*1000)}") - self.assertTrue(self.result, [totalTime, meanTime, minTime, maxTime]) - - async def test_wait_2(self): - macro = parse("wait(10,100).key(1)", self.context, DummyMapping, True) - one_code = system_mapping.get("1") + start = time.time() + await macro.run(self.handler) + time_per_iteration = (time.time() - start) / 5 - minTime = 999999 - maxTime = 0 - its = 10 - totalStart = time.time() - for loopcount in range(its): - runStart = time.time() - await macro.run(self.handler) - runEnd = time.time() - runTime = runEnd - runStart - if runTime < minTime: - minTime = runTime - - if runTime > maxTime: - maxTime = runTime - - totalEnd = time.time() - totalTime = totalEnd - totalStart - meanTime = totalTime / its - print(f"Runtimes min, mean, max {(minTime*1000, meanTime*1000, maxTime*1000)}") - self.assertTrue(self.result, [totalTime, meanTime, minTime, maxTime]) - - async def test_wait_3(self): - macro = parse("set(a,100).wait(10, $a).key(1)", self.context, DummyMapping, True) - one_code = system_mapping.get("1") + self.assertLess(abs(time_per_iteration - 0.05), 0.005) - minTime = 999999 - maxTime = 0 - its = 10 - totalStart = time.time() - for loopcount in range(its): - runStart = time.time() - await macro.run(self.handler) - runEnd = time.time() - runTime = runEnd - runStart - if runTime < minTime: - minTime = runTime + async def test_wait_2_ranged(self): + mapping = DummyMapping() + mapping.macro_key_sleep_ms = 0 + macro = parse("repeat(5, wait(49,51))", self.context, mapping, True) + + start = time.time() + await macro.run(self.handler) + time_per_iteration = (time.time() - start) / 5 + + self.assertLess(abs(time_per_iteration - 0.05), 0.005) - if runTime > maxTime: - maxTime = runTime + async def test_wait_3_ranged_single_get(self): + mapping = DummyMapping() + mapping.macro_key_sleep_ms = 0 + macro = parse("set(a,51).repeat(5, wait(49, $a))", self.context, mapping, True) - totalEnd = time.time() - totalTime = totalEnd - totalStart - meanTime = totalTime / its - print(f"Runtimes min, mean, max {(minTime*1000, meanTime*1000, maxTime*1000)}") - self.assertTrue(self.result, [totalTime, meanTime, minTime, maxTime]) + start = time.time() + await macro.run(self.handler) + time_per_iteration = (time.time() - start) / 5 + + self.assertLess(abs(time_per_iteration - 0.05), 0.005) + + async def test_wait_4_ranged_double_get(self): + mapping = DummyMapping() + mapping.macro_key_sleep_ms = 0 + macro = parse("set(a,49).set(b,51).repeat(5, wait($a, $b))", self.context, mapping, True) + + start = time.time() + await macro.run(self.handler) + time_per_iteration = (time.time() - start) / 5 + self.assertLess(abs(time_per_iteration - 0.05), 0.005) + async def test_duplicate_run(self): # it won't restart the macro, because that may screw up the # internal state (in particular the _trigger_release_event). From 82361d8a03e4ef489566d9b2a957fde1ad5b9110 Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Tue, 22 Oct 2024 11:06:15 +1300 Subject: [PATCH 09/10] Floating time ranges --- inputremapper/injection/macros/macro.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index 76ab73089..dcbfe7a27 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -541,20 +541,23 @@ async def task(handler: Callable): self.tasks.append(task) - def add_wait(self, time: Union[str, int], max_time=None): + def add_wait(self, time: Union[str, int, float], max_time=None): """Wait time in milliseconds.""" - time = _type_check(time, [int], "wait", 1) - max_time = _type_check(max_time, [int,None], "wait", 2) + time = _type_check(time, [int, float], "wait", 1) + max_time = _type_check(max_time, [int, float, None], "wait", 2) async def task(_): - resolved_min_time = _resolve(time, [int]) - resolved_max_time = _resolve(max_time, [int]) + resolved_min_time = _resolve(time, [int, float]) + resolved_max_time = _resolve(max_time, [int, float]) if resolved_max_time is not None and resolved_max_time > resolved_min_time: - variabletime = random.randint(resolved_min_time, resolved_max_time) + logger.debug('Wait time is between "%f" and "%f"ms', resolved_min_time, resolved_max_time) + variabletime = random.uniform(resolved_min_time, resolved_max_time) else: variabletime = resolved_min_time + logger.debug('Wait time used is "%f"ms', variabletime) + await asyncio.sleep(variabletime / 1000) self.tasks.append(task) From d01fa92943c56686437d9618aa7963e508de90da Mon Sep 17 00:00:00 2001 From: Samuel Hawarden Date: Tue, 22 Oct 2024 11:44:24 +1300 Subject: [PATCH 10/10] float was being overridden by int --- inputremapper/injection/macros/macro.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/inputremapper/injection/macros/macro.py b/inputremapper/injection/macros/macro.py index dcbfe7a27..ca0415699 100644 --- a/inputremapper/injection/macros/macro.py +++ b/inputremapper/injection/macros/macro.py @@ -541,22 +541,22 @@ async def task(handler: Callable): self.tasks.append(task) - def add_wait(self, time: Union[str, int, float], max_time=None): + def add_wait(self, time: Union[str, float, int], max_time=None): """Wait time in milliseconds.""" - time = _type_check(time, [int, float], "wait", 1) - max_time = _type_check(max_time, [int, float, None], "wait", 2) + time = _type_check(time, [float, int], "wait", 1) + max_time = _type_check(max_time, [float, int, None], "wait", 2) async def task(_): - resolved_min_time = _resolve(time, [int, float]) - resolved_max_time = _resolve(max_time, [int, float]) + resolved_min_time = _resolve(time, [float, int]) + resolved_max_time = _resolve(max_time, [float, int]) if resolved_max_time is not None and resolved_max_time > resolved_min_time: - logger.debug('Wait time is between "%f" and "%f"ms', resolved_min_time, resolved_max_time) variabletime = random.uniform(resolved_min_time, resolved_max_time) + logger.debug('Wait time %f <= [%f] <= %fms', resolved_min_time, variabletime, resolved_max_time) else: variabletime = resolved_min_time + logger.debug('Wait time used is %fms', variabletime) - logger.debug('Wait time used is "%f"ms', variabletime) await asyncio.sleep(variabletime / 1000)