mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-20 13:54:36 +01:00
pytest: Remove test_lightningd and all the legacy testing framework
This commit is contained in:
parent
d3731b08b1
commit
ae99e493b8
4 changed files with 9 additions and 286 deletions
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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…
Add table
Reference in a new issue