From e16ed0e207d66f01245b249a6f4bad6be1867859 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 30 Oct 2020 11:43:42 +1030 Subject: [PATCH] pyln: add support for dependent hooks. And use that to add simple tests. Signed-off-by: Rusty Russell --- contrib/pyln-client/pyln/client/plugin.py | 22 ++++++++++++++++++---- tests/plugins/dep_a.py | 15 +++++++++++++++ tests/plugins/dep_b.py | 15 +++++++++++++++ tests/plugins/dep_c.py | 15 +++++++++++++++ tests/test_plugin.py | 23 +++++++++++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100755 tests/plugins/dep_a.py create mode 100755 tests/plugins/dep_b.py create mode 100755 tests/plugins/dep_c.py diff --git a/contrib/pyln-client/pyln/client/plugin.py b/contrib/pyln-client/pyln/client/plugin.py index 4be219f4e..3678bb90b 100644 --- a/contrib/pyln-client/pyln/client/plugin.py +++ b/contrib/pyln-client/pyln/client/plugin.py @@ -57,6 +57,8 @@ class Method(object): self.desc = desc self.long_desc = long_desc self.deprecated = deprecated + self.before: List[str] = [] + self.after: List[str] = [] class Request(dict): @@ -405,7 +407,9 @@ class Plugin(object): return decorator def add_hook(self, name: str, func: Callable[..., JSONType], - background: bool = False) -> None: + background: bool = False, + before: Optional[List[str]] = None, + after: Optional[List[str]] = None) -> None: """Register a hook that is called synchronously by lightningd on events """ if name in self.methods: @@ -427,15 +431,23 @@ class Plugin(object): method = Method(name, func, MethodType.HOOK) method.background = background + method.before = [] + if before: + method.before = before + method.after = [] + if after: + method.after = after self.methods[name] = method - def hook(self, method_name: str) -> JsonDecoratorType: + def hook(self, method_name: str, + before: List[str] = None, + after: List[str] = None) -> JsonDecoratorType: """Decorator to add a plugin hook to the dispatch table. Internally uses add_hook. """ def decorator(f: Callable[..., JSONType]) -> Callable[..., JSONType]: - self.add_hook(method_name, f, background=False) + self.add_hook(method_name, f, background=False, before=before, after=after) return f return decorator @@ -689,7 +701,9 @@ class Plugin(object): continue if method.mtype == MethodType.HOOK: - hooks.append(method.name) + hooks.append({'name': method.name, + 'before': method.before, + 'after': method.after}) continue doc = inspect.getdoc(method.func) diff --git a/tests/plugins/dep_a.py b/tests/plugins/dep_a.py new file mode 100755 index 000000000..be1af29ed --- /dev/null +++ b/tests/plugins/dep_a.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +"""A simple plugin that must come before dep_b. +""" +plugin = Plugin() + + +@plugin.hook('htlc_accepted', before=['dep_b.py']) +def on_htlc_accepted(htlc, plugin, **kwargs): + print("htlc_accepted called") + return {'result': 'continue'} + + +plugin.run() diff --git a/tests/plugins/dep_b.py b/tests/plugins/dep_b.py new file mode 100755 index 000000000..d11045728 --- /dev/null +++ b/tests/plugins/dep_b.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +"""A simple plugin that must come after dep_a, before dep_c. +""" +plugin = Plugin() + + +@plugin.hook('htlc_accepted', before=['dep_c.py'], after=['dep_a.py']) +def on_htlc_accepted(htlc, plugin, **kwargs): + print("htlc_accepted called") + return {'result': 'continue'} + + +plugin.run() diff --git a/tests/plugins/dep_c.py b/tests/plugins/dep_c.py new file mode 100755 index 000000000..6c8eb656b --- /dev/null +++ b/tests/plugins/dep_c.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +"""A simple plugin that must come before dep_a. +""" +plugin = Plugin() + + +@plugin.hook('htlc_accepted', before=['dep_a.py']) +def on_htlc_accepted(htlc, plugin, **kwargs): + print("htlc_accepted called") + return {'result': 'continue'} + + +plugin.run() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index e31291cde..2f612f970 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2075,3 +2075,26 @@ def test_htlc_accepted_hook_failcodes(node_factory): inv = l2.rpc.invoice(42, 'failcode{}'.format(failcode), '')['bolt11'] with pytest.raises(RpcError, match=r'failcodename.: .{}.'.format(expected)): l1.rpc.pay(inv) + + +def test_hook_dep(node_factory): + dep_a = os.path.join(os.path.dirname(__file__), 'plugins/dep_a.py') + dep_b = os.path.join(os.path.dirname(__file__), 'plugins/dep_b.py') + dep_c = os.path.join(os.path.dirname(__file__), 'plugins/dep_c.py') + l1, l2 = node_factory.line_graph(2, opts=[{}, {'plugin': dep_b}]) + + # A says it has to be before B. + l2.rpc.plugin_start(plugin=dep_a) + l2.daemon.wait_for_log(r"started.*dep_a.py") + + l1.pay(l2, 100000) + # They must be called in this order! + l2.daemon.wait_for_log(r"dep_a.py: htlc_accepted called") + l2.daemon.wait_for_log(r"dep_b.py: htlc_accepted called") + + # But depc will not load, due to cyclical dep + with pytest.raises(RpcError, match=r'Cannot correctly order hook htlc_accepted'): + l2.rpc.plugin_start(plugin=dep_c) + + l1.rpc.plugin_start(plugin=dep_c) + l1.daemon.wait_for_log(r"started.*dep_c.py")