Browse Source
This is the first example of the py.test style fixtures which should allow us to write much cleaner and nicer tests. Signed-off-by: Christian Decker <decker.christian@gmail.com>ppa-0.6.1
Christian Decker
7 years ago
committed by
Rusty Russell
4 changed files with 207 additions and 54 deletions
@ -0,0 +1,144 @@ |
|||
from concurrent import futures |
|||
from test_lightningd import NodeFactory |
|||
|
|||
import logging |
|||
import os |
|||
import pytest |
|||
import re |
|||
import tempfile |
|||
import utils |
|||
|
|||
|
|||
TEST_DIR = tempfile.mkdtemp(prefix='ltests-') |
|||
VALGRIND = os.getenv("NO_VALGRIND", "0") == "0" |
|||
DEVELOPER = os.getenv("DEVELOPER", "0") == "1" |
|||
TEST_DEBUG = os.getenv("TEST_DEBUG", "0") == "1" |
|||
|
|||
|
|||
@pytest.fixture |
|||
def directory(test_name): |
|||
"""Return a per-test specific directory |
|||
""" |
|||
global TEST_DIR |
|||
yield os.path.join(TEST_DIR, test_name) |
|||
|
|||
|
|||
@pytest.fixture |
|||
def test_name(request): |
|||
yield request.function.__name__ |
|||
|
|||
|
|||
@pytest.fixture |
|||
def bitcoind(directory): |
|||
bitcoind = utils.BitcoinD(bitcoin_dir=directory, rpcport=28332) |
|||
try: |
|||
bitcoind.start() |
|||
except Exception: |
|||
bitcoind.stop() |
|||
raise |
|||
|
|||
info = bitcoind.rpc.getnetworkinfo() |
|||
|
|||
if info['version'] < 160000: |
|||
bitcoind.rpc.stop() |
|||
raise ValueError("bitcoind is too old. At least version 16000 (v0.16.0)" |
|||
" is needed, current version is {}".format(info['version'])) |
|||
|
|||
info = bitcoind.rpc.getblockchaininfo() |
|||
# Make sure we have some spendable funds |
|||
if info['blocks'] < 101: |
|||
bitcoind.generate_block(101 - info['blocks']) |
|||
elif bitcoind.rpc.getwalletinfo()['balance'] < 1: |
|||
logging.debug("Insufficient balance, generating 1 block") |
|||
bitcoind.generate_block(1) |
|||
|
|||
yield bitcoind |
|||
|
|||
try: |
|||
bitcoind.rpc.stop() |
|||
except Exception: |
|||
bitcoind.proc.kill() |
|||
bitcoind.proc.wait() |
|||
|
|||
|
|||
@pytest.fixture |
|||
def node_factory(directory, test_name, bitcoind, executor): |
|||
nf = NodeFactory(test_name, bitcoind, executor, directory=directory) |
|||
yield nf |
|||
err_count = 0 |
|||
ok = nf.killall([not n.may_fail for n in nf.nodes]) |
|||
if VALGRIND: |
|||
for node in nf.nodes: |
|||
err_count += printValgrindErrors(node) |
|||
if err_count: |
|||
raise ValueError("{} nodes reported valgrind errors".format(err_count)) |
|||
|
|||
for node in nf.nodes: |
|||
err_count += printCrashLog(node) |
|||
if err_count: |
|||
raise ValueError("{} nodes had crash.log files".format(err_count)) |
|||
for node in nf.nodes: |
|||
err_count += checkReconnect(node) |
|||
if err_count: |
|||
raise ValueError("{} nodes had unexpected reconnections".format(err_count)) |
|||
|
|||
if not ok: |
|||
raise Exception("At least one lightning exited with unexpected non-zero return code") |
|||
|
|||
|
|||
def getValgrindErrors(node): |
|||
for error_file in os.listdir(node.daemon.lightning_dir): |
|||
if not re.fullmatch("valgrind-errors.\d+", error_file): |
|||
continue |
|||
with open(os.path.join(node.daemon.lightning_dir, error_file), 'r') as f: |
|||
errors = f.read().strip() |
|||
if errors: |
|||
return errors, error_file |
|||
return None, None |
|||
|
|||
|
|||
def printValgrindErrors(node): |
|||
errors, fname = getValgrindErrors(node) |
|||
if errors: |
|||
print("-" * 31, "Valgrind errors", "-" * 32) |
|||
print("Valgrind error file:", fname) |
|||
print(errors) |
|||
print("-" * 80) |
|||
return 1 if errors else 0 |
|||
|
|||
|
|||
def getCrashLog(node): |
|||
if node.may_fail: |
|||
return None, None |
|||
try: |
|||
crashlog = os.path.join(node.daemon.lightning_dir, 'crash.log') |
|||
with open(crashlog, 'r') as f: |
|||
return f.readlines(), crashlog |
|||
except Exception: |
|||
return None, None |
|||
|
|||
|
|||
def printCrashLog(node): |
|||
errors, fname = getCrashLog(node) |
|||
if errors: |
|||
print("-" * 10, "{} (last 50 lines)".format(fname), "-" * 10) |
|||
for l in errors[-50:]: |
|||
print(l, end='') |
|||
print("-" * 80) |
|||
return 1 if errors else 0 |
|||
|
|||
|
|||
def checkReconnect(node): |
|||
# Without DEVELOPER, we can't suppress reconnection. |
|||
if node.may_reconnect or not DEVELOPER: |
|||
return 0 |
|||
if node.daemon.is_in_log('Peer has reconnected'): |
|||
return 1 |
|||
return 0 |
|||
|
|||
|
|||
@pytest.fixture |
|||
def executor(): |
|||
ex = futures.ThreadPoolExecutor(max_workers=20) |
|||
yield ex |
|||
ex.shutdown(wait=False) |
@ -0,0 +1,60 @@ |
|||
from fixtures import * # noqa: F401,F403 |
|||
from test_lightningd import wait_for |
|||
|
|||
import os |
|||
import time |
|||
import unittest |
|||
|
|||
|
|||
DEVELOPER = os.getenv("DEVELOPER", "0") == "1" |
|||
|
|||
|
|||
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") |
|||
def test_gossip_pruning(node_factory, bitcoind): |
|||
""" Create channel and see it being updated in time before pruning |
|||
""" |
|||
opts = {'channel-update-interval': 5} |
|||
l1, l2, l3 = node_factory.get_nodes(3, opts) |
|||
|
|||
l1.rpc.connect(l2.info['id'], 'localhost', l2.info['port']) |
|||
l2.rpc.connect(l3.info['id'], 'localhost', l3.info['port']) |
|||
|
|||
scid1 = l1.fund_channel(l2, 10**6) |
|||
scid2 = l2.fund_channel(l3, 10**6) |
|||
|
|||
bitcoind.rpc.generate(6) |
|||
|
|||
# Channels should be activated locally |
|||
wait_for(lambda: [c['active'] for c in l1.rpc.listchannels()['channels']] == [True] * 4) |
|||
wait_for(lambda: [c['active'] for c in l2.rpc.listchannels()['channels']] == [True] * 4) |
|||
wait_for(lambda: [c['active'] for c in l3.rpc.listchannels()['channels']] == [True] * 4) |
|||
|
|||
# All of them should send a keepalive message |
|||
l1.daemon.wait_for_logs([ |
|||
'Sending keepalive channel_update for {}'.format(scid1), |
|||
]) |
|||
l2.daemon.wait_for_logs([ |
|||
'Sending keepalive channel_update for {}'.format(scid1), |
|||
'Sending keepalive channel_update for {}'.format(scid2), |
|||
]) |
|||
l3.daemon.wait_for_logs([ |
|||
'Sending keepalive channel_update for {}'.format(scid2), |
|||
]) |
|||
|
|||
# Now kill l3, so that l2 and l1 can prune it from their view after 10 seconds |
|||
|
|||
# FIXME: This sleep() masks a real bug: that channeld sends a |
|||
# channel_update message (to disable the channel) with same |
|||
# timestamp as the last keepalive, and thus is ignored. The minimal |
|||
# fix is to backdate the keepalives 1 second, but maybe we should |
|||
# simply have gossipd generate all updates? |
|||
time.sleep(1) |
|||
l3.stop() |
|||
|
|||
l1.daemon.wait_for_log("Pruning channel {} from network view".format(scid2)) |
|||
l2.daemon.wait_for_log("Pruning channel {} from network view".format(scid2)) |
|||
|
|||
assert scid2 not in [c['short_channel_id'] for c in l1.rpc.listchannels()['channels']] |
|||
assert scid2 not in [c['short_channel_id'] for c in l2.rpc.listchannels()['channels']] |
|||
assert l3.info['id'] not in [n['nodeid'] for n in l1.rpc.listnodes()['nodes']] |
|||
assert l3.info['id'] not in [n['nodeid'] for n in l2.rpc.listnodes()['nodes']] |
Loading…
Reference in new issue