pyln: add support for dependent hooks.

And use that to add simple tests.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2020-10-30 11:43:42 +10:30 committed by neil saitug
parent 6a55b4367e
commit e16ed0e207
5 changed files with 86 additions and 4 deletions

View File

@ -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)

15
tests/plugins/dep_a.py Executable file
View File

@ -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()

15
tests/plugins/dep_b.py Executable file
View File

@ -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()

15
tests/plugins/dep_c.py Executable file
View File

@ -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()

View File

@ -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")