You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

174 lines
6.7 KiB

import os
import re
import subprocess
from shlex import split
from time import sleep
from typing import Any, Tuple
import pytest
def get_bitcoind_version(bitcoind_path: str, conf: str) -> Tuple[int, int]:
"""
This utility function returns the bitcoind version number
as a tuple in the form (major, minor)
"""
version = local_command(f'{bitcoind_path} -version -conf={conf}')
if version.returncode != 0:
raise RuntimeError(version.stdout.decode('utf-8'))
version_string = version.stdout.split(b'\n')[0]
version_tuple = re.match(
br'.*v(?P<major>\d+)\.(?P<minor>\d+)', version_string).groups()
major, minor = map(lambda x: int(x), version_tuple)
return major, minor
def local_command(command: str, bg: bool = False):
"""
Execute command in a new process.
"""
command = split(command)
if bg:
# using subprocess.PIPE seems to cause problems
FNULL = open(os.devnull, 'w')
return subprocess.Popen(command,
stdout=FNULL,
stderr=subprocess.STDOUT,
close_fds=True)
# in case of foreground execution, we can use the output; if not
# it doesn't matter
return subprocess.run(command, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
def root_path() -> str:
"""
Returns the directory in which this file is contained.
"""
return os.path.dirname(os.path.realpath(__file__))
def btc_conf_test_path() -> str:
"""
Returns default Bitcoin conf test path.
"""
return os.path.join(root_path(), 'test/bitcoin.conf')
def pytest_addoption(parser: Any) -> None:
"""
Pytest initialization hook to register argparse-style options.
"""
parser.addoption("--btcroot", action="store", default='',
help="the fully qualified path to the directory containing " +
"the bitcoin binaries, e.g. /home/user/bitcoin/bin/")
parser.addoption("--btcconf", action="store",
default=btc_conf_test_path(),
help="the fully qualified path to the location of the " +
"bitcoin configuration file you use for testing, e.g. " +
"/home/user/.bitcoin/bitcoin.conf")
parser.addoption("--btcpwd",
action="store",
help="the RPC password for your test bitcoin instance")
parser.addoption("--btcuser",
action="store",
default='bitcoinrpc',
help="the RPC username for your test bitcoin instance (default=bitcoinrpc)")
parser.addoption("--nirc",
type="int",
action="store",
default=1,
help="the number of local miniircd instances")
@pytest.fixture(scope="session", autouse=True)
def setup_early_if_needed(request) -> None:
"""
Make sure fixtures requested by test *modules* are executed first.
I.e., like a dynamically set `autouse=True`.
(By default, without `autouse=True`, they would run later at request time)
Useful so that fixtures like `setup_regtest_bitcoind` can run *only* if
we are planning to invoke a test that requires it, but still at startup time.
"""
modules = set()
# Loop through the collected tests
for item in request.node.items:
module = item.getparent(pytest.Module)
if module in modules:
continue
modules.add(module)
# Loop through each test module marker
for marker in module.iter_markers('usefixtures'):
# We know we are gonna need these fixtures, so we invoke them early
for fixture in marker.args:
request.getfixturevalue(fixture)
@pytest.fixture(scope="session")
def setup_miniircd(pytestconfig):
"""
Setup miniircd and handle its clean up.
"""
miniircd_procs = []
cwd = os.getcwd()
n_irc = pytestconfig.getoption("--nirc")
miniircd_path = os.path.join(root_path(), 'miniircd', 'miniircd')
# minor bug in miniircd (seems); need *full* unqualified path for motd file
motd_path = os.path.join(cwd, 'miniircd', 'testmotd')
for i in range(n_irc):
command = f"{miniircd_path} --ports={16667 + i} --motd={motd_path}"
miniircd_proc = local_command(command, bg=True)
miniircd_procs.append(miniircd_proc)
yield
# didn't find a stop command in miniircd, so just kill
for m in miniircd_procs:
m.kill()
@pytest.fixture(scope="session")
def setup_regtest_bitcoind(pytestconfig):
"""
Setup regtest bitcoind and handle its clean up.
"""
conf = pytestconfig.getoption("--btcconf")
rpcuser = pytestconfig.getoption("--btcuser")
rpcpassword = pytestconfig.getoption("--btcpwd")
bitcoin_path = pytestconfig.getoption("--btcroot")
bitcoind_path = os.path.join(bitcoin_path, "bitcoind")
bitcoincli_path = os.path.join(bitcoin_path, "bitcoin-cli")
start_cmd = f'{bitcoind_path} -regtest -daemon -conf={conf}'
stop_cmd = f'{bitcoincli_path} -regtest -rpcuser={rpcuser} -rpcpassword={rpcpassword} stop'
# determine bitcoind version
try:
bitcoind_version = get_bitcoind_version(bitcoind_path, conf)
except RuntimeError as exc:
pytest.exit(f"Cannot setup tests, bitcoind failing.\n{exc}")
if bitcoind_version[0] >= 26:
start_cmd += ' -allowignoredconf=1'
local_command(start_cmd, bg=True)
root_cmd = f'{bitcoincli_path} -regtest -rpcuser={rpcuser} -rpcpassword={rpcpassword}'
wallet_name = 'jm-test-wallet'
# Bitcoin Core v0.21+ does not create default wallet
# From Bitcoin Core 0.21.0 there is support for descriptor wallets, which
# are default from 23.x+ (including 22.99.0 development versions).
# We don't support descriptor wallets yet.
if bitcoind_version[0] >= 22:
create_wallet = f'{root_cmd} -rpcwait -named createwallet wallet_name={wallet_name} descriptors=false'
else:
create_wallet = f'{root_cmd} -rpcwait createwallet {wallet_name}'
local_command(create_wallet)
local_command(f'{root_cmd} loadwallet {wallet_name}')
for i in range(2):
cpe = local_command(f'{root_cmd} -rpcwallet={wallet_name} getnewaddress')
if cpe.returncode != 0:
pytest.exit(f"Cannot setup tests, bitcoin-cli failing.\n{cpe.stdout.decode('utf-8')}")
destn_addr = cpe.stdout[:-1].decode('utf-8')
local_command(f'{root_cmd} -rpcwallet={wallet_name} generatetoaddress 301 {destn_addr}')
sleep(1)
yield
# shut down bitcoind
local_command(stop_cmd)
# note, it is better to clean out ~/.bitcoin/regtest but too
# dangerous to automate it here perhaps