Merge pull request #114 from cdecker/pytest-new

pytest: Add support for both legacy and new daemons
This commit is contained in:
Rusty Russell 2017-01-24 13:36:47 +10:30 committed by GitHub
commit 1c49af7486
6 changed files with 102 additions and 43 deletions

View File

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

View File

@ -7,28 +7,12 @@ import socket
import sys
import threading
class LightningRpc(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.
"""
class UnixDomainSocketRpc(object):
def __init__(self, socket_path, executor=None):
self.socket_path = socket_path
self.socket = None
self.buff = b''
self.decoder = json.JSONDecoder()
self.executor = executor
def connect_rpc(self):
pass
def _writeobj(self, sock, obj):
s = json.dumps(obj)
sock.sendall(bytearray(s, 'UTF-8'))
@ -66,6 +50,58 @@ class LightningRpc(object):
raise ValueError("Malformed response, 'result' missing.")
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):
"""List all known channels.
"""

View File

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

View File

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

View File

@ -1,7 +1,7 @@
from binascii import hexlify, unhexlify
from concurrent import futures
from hashlib import sha256
from utils import BitcoinD, LightningD, LightningRpc, LightningNode
from lightning import LightningRpc, LegacyLightningRpc
import logging
import os
@ -9,6 +9,7 @@ import sys
import tempfile
import time
import unittest
import utils
bitcoind = None
TEST_DIR = tempfile.mkdtemp(prefix='lightning-')
@ -21,7 +22,7 @@ logging.info("Tests running in '%s'", TEST_DIR)
def setupBitcoind():
global bitcoind
bitcoind = BitcoinD(rpcport=28332)
bitcoind = utils.BitcoinD(rpcport=28332)
bitcoind.start()
info = bitcoind.rpc.getinfo()
# Make sure we have segwit and some funds
@ -59,7 +60,7 @@ class NodeFactory(object):
self.nodes = []
self.executor = executor
def get_node(self):
def get_node(self, legacy=True):
node_id = self.next_id
self.next_id += 1
@ -67,9 +68,15 @@ class NodeFactory(object):
TEST_DIR, self.func._testMethodName, "lightning-{}/".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)
rpc = LightningRpc(socket_path, self.executor)
node = LightningNode(daemon, rpc, bitcoind, self.executor)
port = 16330+node_id
if legacy:
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)
if VALGRIND:
node.daemon.cmd_line = [
@ -80,7 +87,6 @@ class NodeFactory(object):
] + node.daemon.cmd_line
node.daemon.start()
node.rpc.connect_rpc()
# Cache `getinfo`, we'll be using it a lot
node.info = node.rpc.getinfo()
return node
@ -90,8 +96,7 @@ class NodeFactory(object):
n.daemon.stop()
class LightningDTests(unittest.TestCase):
class BaseLightningDTests(unittest.TestCase):
def setUp(self):
# Most of the executor threads will be waiting for IO, so
# let's have a few of them
@ -103,6 +108,23 @@ class LightningDTests(unittest.TestCase):
self.executor.shutdown(wait=False)
# 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):
l1 = self.node_factory.get_node()
l2 = self.node_factory.get_node()

View File

@ -1,4 +1,4 @@
from bitcoinrpc.authproxy import AuthServiceProxy
from bitcoin.rpc import RawProxy as BitcoinProxy
from lightning import LightningRpc
import logging
@ -108,17 +108,6 @@ class TailableProc(object):
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):
def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=18332):
@ -145,7 +134,7 @@ class BitcoinD(TailableProc):
BITCOIND_CONFIG['rpcport'] = rpcport
write_config(os.path.join(bitcoin_dir, '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))
def start(self):
@ -160,7 +149,7 @@ class LightningD(TailableProc):
self.lightning_dir = lightning_dir
self.port = port
self.cmd_line = [
'daemon/lightningd',
'lightningd/lightningd',
'--bitcoin-datadir={}'.format(bitcoin_dir),
'--lightning-dir={}'.format(lightning_dir),
'--port={}'.format(port),
@ -176,13 +165,24 @@ class LightningD(TailableProc):
def start(self):
TailableProc.start(self)
self.wait_for_log("Hello world!")
self.wait_for_log("Creating IPv6 listener on port")
logging.info("LightningD started")
def stop(self):
TailableProc.stop(self)
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):
def __init__(self, daemon, rpc, btc, executor):
self.rpc = rpc