Browse Source

Merge pull request #114 from cdecker/pytest-new

pytest: Add support for both legacy and new daemons
ppa-0.6.1
Rusty Russell 8 years ago
committed by GitHub
parent
commit
1c49af7486
  1. 2
      contrib/pylightning/lightning/__init__.py
  2. 70
      contrib/pylightning/lightning/lightning.py
  3. 1
      daemon/log.c
  4. 2
      tests/requirements.txt
  5. 40
      tests/test_lightningd.py
  6. 30
      tests/utils.py

2
contrib/pylightning/lightning/__init__.py

@ -1 +1 @@
from .lightning import LightningRpc from .lightning import LightningRpc, LegacyLightningRpc

70
contrib/pylightning/lightning/lightning.py

@ -7,28 +7,12 @@ import socket
import sys import sys
import threading import threading
class LightningRpc(object): class UnixDomainSocketRpc(object):
"""RPC client for the `lightningd` daemon.
This RPC client connects to the `lightningd` daemon through a unix
domain socket and passes calls through. Since some of the calls
are blocking, the corresponding python methods include an `async`
keyword argument. If `async` is set to true then the method
returns a future immediately, instead of blocking indefinitely.
This implementation is thread safe in that it locks the socket
between calls, but it does not (yet) support concurrent calls.
"""
def __init__(self, socket_path, executor=None): def __init__(self, socket_path, executor=None):
self.socket_path = socket_path self.socket_path = socket_path
self.socket = None
self.buff = b''
self.decoder = json.JSONDecoder() self.decoder = json.JSONDecoder()
self.executor = executor self.executor = executor
def connect_rpc(self):
pass
def _writeobj(self, sock, obj): def _writeobj(self, sock, obj):
s = json.dumps(obj) s = json.dumps(obj)
sock.sendall(bytearray(s, 'UTF-8')) sock.sendall(bytearray(s, 'UTF-8'))
@ -66,6 +50,58 @@ class LightningRpc(object):
raise ValueError("Malformed response, 'result' missing.") raise ValueError("Malformed response, 'result' missing.")
return resp['result'] return resp['result']
class LightningRpc(UnixDomainSocketRpc):
"""RPC client for the `lightningd` daemon.
This RPC client connects to the `lightningd` daemon through a unix
domain socket and passes calls through. Since some of the calls
are blocking, the corresponding python methods include an `async`
keyword argument. If `async` is set to true then the method
returns a future immediately, instead of blocking indefinitely.
This implementation is thread safe in that it locks the socket
between calls, but it does not (yet) support concurrent calls.
"""
def connect(self, hostname, port, remote_id):
return self._call("connect", [hostname, port, remote_id])
def getpeers(self):
return self._call("getpeers", [])
def getpeer(self, peer_id):
"""Get info about a specific peer.
"""
peers = self.getpeers()['peers']
for p in peers:
if p['peerid'] == peer_id:
return p
return None
def stop(self):
return self._call("stop", [])
def getlog(self, level=None):
args = []
if level is not None:
args.append(level)
return self._call("getlog", args)
def getinfo(self):
return self._call("getinfo", [])
def dev_add_route(self, src, dst, base, var, delay, minblocks):
"""Add route from {src} to {dst}, {base} rate in msatoshi, {var} rate in msatoshi, {delay} blocks delay and {minblocks} minimum timeout
"""
return self._call("dev-add-route", [src, dst, base, var, delay, minblocks])
def getchannels(self):
return self._call("getchannels", [])
def getnodes(self):
return self._call("getnodes", [])
class LegacyLightningRpc(UnixDomainSocketRpc):
def getchannels(self): def getchannels(self):
"""List all known channels. """List all known channels.
""" """

1
daemon/log.c

@ -421,6 +421,7 @@ static void log_to_file(const char *prefix,
} else { } else {
fprintf(logf, "%s \t%s\n", prefix, str); fprintf(logf, "%s \t%s\n", prefix, str);
} }
fflush(logf);
} }
static char *arg_log_to_file(const char *arg, struct log *log) static char *arg_log_to_file(const char *arg, struct log *log)

2
tests/requirements.txt

@ -1 +1 @@
python-bitcoinrpc==1.0 python-bitcoinlib==0.7.0

40
tests/test_lightningd.py

@ -1,7 +1,7 @@
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from concurrent import futures from concurrent import futures
from hashlib import sha256 from hashlib import sha256
from utils import BitcoinD, LightningD, LightningRpc, LightningNode from lightning import LightningRpc, LegacyLightningRpc
import logging import logging
import os import os
@ -9,6 +9,7 @@ import sys
import tempfile import tempfile
import time import time
import unittest import unittest
import utils
bitcoind = None bitcoind = None
TEST_DIR = tempfile.mkdtemp(prefix='lightning-') TEST_DIR = tempfile.mkdtemp(prefix='lightning-')
@ -21,7 +22,7 @@ logging.info("Tests running in '%s'", TEST_DIR)
def setupBitcoind(): def setupBitcoind():
global bitcoind global bitcoind
bitcoind = BitcoinD(rpcport=28332) bitcoind = utils.BitcoinD(rpcport=28332)
bitcoind.start() bitcoind.start()
info = bitcoind.rpc.getinfo() info = bitcoind.rpc.getinfo()
# Make sure we have segwit and some funds # Make sure we have segwit and some funds
@ -59,7 +60,7 @@ class NodeFactory(object):
self.nodes = [] self.nodes = []
self.executor = executor self.executor = executor
def get_node(self): def get_node(self, legacy=True):
node_id = self.next_id node_id = self.next_id
self.next_id += 1 self.next_id += 1
@ -67,9 +68,15 @@ class NodeFactory(object):
TEST_DIR, self.func._testMethodName, "lightning-{}/".format(node_id)) TEST_DIR, self.func._testMethodName, "lightning-{}/".format(node_id))
socket_path = os.path.join(lightning_dir, "lightning-rpc").format(node_id) socket_path = os.path.join(lightning_dir, "lightning-rpc").format(node_id)
daemon = LightningD(lightning_dir, bitcoind.bitcoin_dir, port=16330+node_id) port = 16330+node_id
rpc = LightningRpc(socket_path, self.executor) if legacy:
node = LightningNode(daemon, rpc, bitcoind, self.executor) daemon = utils.LegacyLightningD(lightning_dir, bitcoind.bitcoin_dir, port=port)
rpc = LegacyLightningRpc(socket_path, self.executor)
else:
daemon = utils.LightningD(lightning_dir, bitcoind.bitcoin_dir, port=port)
rpc = LightningRpc(socket_path, self.executor)
node = utils.LightningNode(daemon, rpc, bitcoind, self.executor)
self.nodes.append(node) self.nodes.append(node)
if VALGRIND: if VALGRIND:
node.daemon.cmd_line = [ node.daemon.cmd_line = [
@ -80,7 +87,6 @@ class NodeFactory(object):
] + node.daemon.cmd_line ] + node.daemon.cmd_line
node.daemon.start() node.daemon.start()
node.rpc.connect_rpc()
# Cache `getinfo`, we'll be using it a lot # Cache `getinfo`, we'll be using it a lot
node.info = node.rpc.getinfo() node.info = node.rpc.getinfo()
return node return node
@ -90,8 +96,7 @@ class NodeFactory(object):
n.daemon.stop() n.daemon.stop()
class LightningDTests(unittest.TestCase): class BaseLightningDTests(unittest.TestCase):
def setUp(self): def setUp(self):
# Most of the executor threads will be waiting for IO, so # Most of the executor threads will be waiting for IO, so
# let's have a few of them # let's have a few of them
@ -103,6 +108,23 @@ class LightningDTests(unittest.TestCase):
self.executor.shutdown(wait=False) self.executor.shutdown(wait=False)
# TODO(cdecker) Check that valgrind didn't find any errors # TODO(cdecker) Check that valgrind didn't find any errors
class LightningDTests(BaseLightningDTests):
def test_connect(self):
l1 = self.node_factory.get_node(legacy=False)
l2 = self.node_factory.get_node(legacy=False)
ret = l1.rpc.connect('localhost', l2.info['port'], l2.info['id'])
assert ret['id'] == l2.info['id']
p1 = l1.rpc.getpeer(l2.info['id'])
p2 = l2.rpc.getpeer(l1.info['id'])
assert p1['condition'] == 'Exchanging gossip'
assert p2['condition'] == 'Exchanging gossip'
class LegacyLightningDTests(BaseLightningDTests):
def test_connect(self): def test_connect(self):
l1 = self.node_factory.get_node() l1 = self.node_factory.get_node()
l2 = self.node_factory.get_node() l2 = self.node_factory.get_node()

30
tests/utils.py

@ -1,4 +1,4 @@
from bitcoinrpc.authproxy import AuthServiceProxy from bitcoin.rpc import RawProxy as BitcoinProxy
from lightning import LightningRpc from lightning import LightningRpc
import logging import logging
@ -108,17 +108,6 @@ class TailableProc(object):
pos += 1 pos += 1
class ThreadSafeAuthServiceProxy(AuthServiceProxy):
"""Thread-safe variant of the AuthServiceProxy.
"""
lock = threading.RLock()
def __call__(self, *args):
with ThreadSafeAuthServiceProxy.lock:
AuthServiceProxy.__call__(self, *args)
class BitcoinD(TailableProc): class BitcoinD(TailableProc):
def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=18332): def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=18332):
@ -145,7 +134,7 @@ class BitcoinD(TailableProc):
BITCOIND_CONFIG['rpcport'] = rpcport BITCOIND_CONFIG['rpcport'] = rpcport
write_config(os.path.join(bitcoin_dir, 'bitcoin.conf'), BITCOIND_CONFIG) write_config(os.path.join(bitcoin_dir, 'bitcoin.conf'), BITCOIND_CONFIG)
write_config(os.path.join(regtestdir, 'bitcoin.conf'), BITCOIND_CONFIG) write_config(os.path.join(regtestdir, 'bitcoin.conf'), BITCOIND_CONFIG)
self.rpc = ThreadSafeAuthServiceProxy( self.rpc = BitcoinProxy(
"http://rpcuser:rpcpass@127.0.0.1:{}".format(self.rpcport)) "http://rpcuser:rpcpass@127.0.0.1:{}".format(self.rpcport))
def start(self): def start(self):
@ -160,7 +149,7 @@ class LightningD(TailableProc):
self.lightning_dir = lightning_dir self.lightning_dir = lightning_dir
self.port = port self.port = port
self.cmd_line = [ self.cmd_line = [
'daemon/lightningd', 'lightningd/lightningd',
'--bitcoin-datadir={}'.format(bitcoin_dir), '--bitcoin-datadir={}'.format(bitcoin_dir),
'--lightning-dir={}'.format(lightning_dir), '--lightning-dir={}'.format(lightning_dir),
'--port={}'.format(port), '--port={}'.format(port),
@ -176,13 +165,24 @@ class LightningD(TailableProc):
def start(self): def start(self):
TailableProc.start(self) TailableProc.start(self)
self.wait_for_log("Hello world!") self.wait_for_log("Creating IPv6 listener on port")
logging.info("LightningD started") logging.info("LightningD started")
def stop(self): def stop(self):
TailableProc.stop(self) TailableProc.stop(self)
logging.info("LightningD stopped") logging.info("LightningD stopped")
class LegacyLightningD(LightningD):
def __init__(self, *args, **kwargs):
LightningD.__init__(self, *args, **kwargs)
self.cmd_line[0] = 'daemon/lightningd'
def start(self):
TailableProc.start(self)
self.wait_for_log("Hello world!")
logging.info("LightningD started")
class LightningNode(object): class LightningNode(object):
def __init__(self, daemon, rpc, btc, executor): def __init__(self, daemon, rpc, btc, executor):
self.rpc = rpc self.rpc = rpc

Loading…
Cancel
Save