Browse Source

pytest: Remove test_lightningd and all the legacy testing framework

ppa-0.6.1
Christian Decker 7 years ago
committed by Rusty Russell
parent
commit
ae99e493b8
  1. 4
      doc/HACKING.md
  2. 17
      tests/benchmark.py
  3. 2
      tests/test_gossip.py
  4. 272
      tests/test_lightningd.py

4
doc/HACKING.md

@ -203,9 +203,9 @@ There are three kinds of tests:
* **blackbox tests** - These test setup a mini-regtest environment and test
lightningd as a whole. They can be run individually:
`PYTHONPATH=contrib/pylightning python3 tests/test_lightningd.py -f`.
`PYTHONPATH=contrib/pylightning py.test -v tests/`.
You can also append `LightningDTests.TESTNAME` to run a single test.
You can also append `-k TESTNAME` to run a single test.
Our Travis CI instance (see `.travis.yml`) runs all these for each
pull request.

17
tests/benchmark.py

@ -1,12 +1,14 @@
from concurrent import futures
from fixtures import * # noqa: F401,F403
from time import time
from tqdm import tqdm
import logging
import pytest
import random
import utils
from concurrent import futures
from test_lightningd import NodeFactory
from time import time
from tqdm import tqdm
num_workers = 480
num_payments = 10000
@ -38,13 +40,6 @@ def bitcoind():
bitcoind.proc.wait()
@pytest.fixture
def node_factory(request, bitcoind, executor):
nf = NodeFactory(request.node.name, bitcoind, executor)
yield nf
nf.killall([False] * len(nf.nodes))
def test_single_hop(node_factory, executor):
l1 = node_factory.get_node()
l2 = node_factory.get_node()

2
tests/test_gossip.py

@ -1,5 +1,5 @@
from fixtures import * # noqa: F401,F403
from test_lightningd import wait_for
from utils import wait_for
import json
import logging

272
tests/test_lightningd.py

@ -1,272 +0,0 @@
from concurrent import futures
from decimal import Decimal
from flaky import flaky
from utils import NodeFactory, wait_for, only_one
import copy
import json
import logging
import os
import random
import re
import shutil
import socket
import sqlite3
import string
import subprocess
import sys
import tempfile
import time
import unittest
import utils
from lightning import RpcError
with open('config.vars') as configfile:
config = dict([(line.rstrip().split('=', 1)) for line in configfile])
bitcoind = None
TEST_DIR = tempfile.mkdtemp(prefix='lightning-')
VALGRIND = os.getenv("VALGRIND", config['VALGRIND']) == "1"
DEVELOPER = os.getenv("DEVELOPER", config['DEVELOPER']) == "1"
TEST_DEBUG = os.getenv("TEST_DEBUG", "0") == "1"
if TEST_DEBUG:
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
logging.info("Tests running in '%s'", TEST_DIR)
def to_json(arg):
return json.loads(json.dumps(arg))
def setupBitcoind(directory):
global bitcoind
bitcoind = utils.BitcoinD(bitcoin_dir=directory, rpcport=None)
try:
bitcoind.start()
except Exception:
teardown_bitcoind()
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)
def wait_forget_channels(node):
"""This node is closing all of its channels, check we are forgetting them
"""
node.daemon.wait_for_log(r'onchaind complete, forgetting peer')
# May have reconnected, but should merely be gossiping.
for peer in node.rpc.listpeers()['peers']:
assert peer['state'] == 'GOSSIPING'
assert node.db_query("SELECT * FROM channels") == []
def sync_blockheight(nodes):
target = nodes[0].bitcoin.rpc.getblockcount()
for n in nodes:
wait_for(lambda: n.rpc.getinfo()['blockheight'] == target)
def teardown_bitcoind():
global bitcoind
try:
bitcoind.rpc.stop()
except Exception:
bitcoind.proc.kill()
bitcoind.proc.wait()
class BaseLightningDTests(unittest.TestCase):
def setUp(self):
bitcoin_dir = os.path.join(TEST_DIR, self._testMethodName, "bitcoind")
setupBitcoind(bitcoin_dir)
# Most of the executor threads will be waiting for IO, so
# let's have a few of them
self.executor = futures.ThreadPoolExecutor(max_workers=20)
self.node_factory = NodeFactory(self._testMethodName, bitcoind, self.executor, directory=TEST_DIR)
def getValgrindErrors(self, 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(self, node):
errors, fname = self.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(self, 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(self, node):
errors, fname = self.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(self, 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
def checkBadGossipOrder(self, node):
# We can have a race where we notice a channel deleted and someone
# sends an update, and we can get unknown channel updates in errors.
if node.daemon.is_in_log('Bad gossip order from (?!error)') and not node.daemon.is_in_log('Deleting channel'):
return 1
return 0
def tearDown(self):
ok = self.node_factory.killall([not n.may_fail for n in self.node_factory.nodes])
self.executor.shutdown(wait=False)
teardown_bitcoind()
err_count = 0
# Do not check for valgrind error files if it is disabled
if VALGRIND:
for node in self.node_factory.nodes:
err_count += self.printValgrindErrors(node)
if err_count:
raise ValueError("{} nodes reported valgrind errors".format(err_count))
for node in self.node_factory.nodes:
err_count += self.printCrashLog(node)
if err_count:
raise ValueError("{} nodes had crash.log files".format(err_count))
for node in self.node_factory.nodes:
err_count += self.checkReconnect(node)
if err_count:
raise ValueError("{} nodes had unexpected reconnections".format(err_count))
for node in self.node_factory.nodes:
err_count += self.checkBadGossipOrder(node)
if err_count:
raise ValueError("{} nodes had bad gossip order".format(err_count))
if not ok:
raise Exception("At least one lightning exited with unexpected non-zero return code")
shutil.rmtree(self.node_factory.directory)
class LightningDTests(BaseLightningDTests):
def connect(self, may_reconnect=False):
l1, l2 = self.node_factory.get_nodes(2, opts={'may_reconnect': may_reconnect})
ret = l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
assert ret['id'] == l2.info['id']
l1.daemon.wait_for_log('Handing back peer .* to master')
l2.daemon.wait_for_log('Handing back peer .* to master')
return l1, l2
# Waits until l1 notices funds
def give_funds(self, l1, satoshi):
addr = l1.rpc.newaddr()['address']
bitcoind.rpc.sendtoaddress(addr, satoshi / 10**8)
numfunds = len(l1.rpc.listfunds()['outputs'])
bitcoind.generate_block(1)
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > numfunds)
# Returns the short channel-id: <blocknum>:<txnum>:<outnum>
def fund_channel(self, l1, l2, amount):
return l1.fund_channel(l2, amount)
def pay(self, lsrc, ldst, amt, label=None, async=False):
if not label:
label = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(20))
rhash = ldst.rpc.invoice(amt, label, label)['payment_hash']
assert only_one(ldst.rpc.listinvoices(label)['invoices'])['status'] == 'unpaid'
routestep = {
'msatoshi': amt,
'id': ldst.info['id'],
'delay': 5,
'channel': '1:1:1'
}
def wait_pay():
# Up to 10 seconds for payment to succeed.
start_time = time.time()
while only_one(ldst.rpc.listinvoices(label)['invoices'])['status'] != 'paid':
if time.time() > start_time + 10:
raise TimeoutError('Payment timed out')
time.sleep(0.1)
# sendpay is async now
lsrc.rpc.sendpay(to_json([routestep]), rhash)
if async:
return self.executor.submit(wait_pay)
else:
# wait for sendpay to comply
lsrc.rpc.waitsendpay(rhash)
# This waits until gossipd sees channel_update in both directions
# (or for local channels, at least a local announcement)
def wait_for_routes(self, l1, channel_ids):
bitcoind.generate_block(5)
# Could happen in any order...
l1.daemon.wait_for_logs(['Received channel_update for channel {}\\(0\\)'.format(c)
for c in channel_ids] +
['Received channel_update for channel {}\\(1\\)'.format(c)
for c in channel_ids])
def fake_bitcoind_fail(self, l1, exitcode):
# Create and rename, for atomicity.
f = os.path.join(l1.daemon.lightning_dir, "bitcoin-cli-fail.tmp")
with open(f, "w") as text_file:
print(exitcode, file=text_file)
os.rename(f, os.path.join(l1.daemon.lightning_dir, "bitcoin-cli-fail"))
def fake_bitcoind_unfail(self, l1):
os.remove(os.path.join(l1.daemon.lightning_dir, "bitcoin-cli-fail"))
def test_features(self):
l1, l2 = self.connect()
# LOCAL_INITIAL_ROUTING_SYNC + LOCAL_GOSSIP_QUERIES
assert only_one(l1.rpc.listpeers()['peers'])['local_features'] == '88'
if __name__ == '__main__':
unittest.main(verbosity=2)
Loading…
Cancel
Save