core-lightning/tests/test_reckless.py
Rusty Russell dd569b0b7a pytest: disable broken test
Seems like it's using an old version of pyln-client, which had the bug
of referring to long_desc:

```
def test_local_dir_install(node_factory):
        """Test search and install from local directory source."""
        n = get_reckless_node(node_factory)
        n.start()
        r = reckless([f"--network={NETWORK}", "-v", "source", "add",
                      "tests/data/recklessrepo/lightningd/testplugpass"],
                     dir=n.lightning_dir)
        assert r.returncode == 0
        r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass"], dir=n.lightning_dir)
        assert r.returncode == 0
>       assert 'testplugpass enabled' in r.stdout
E       assert 'testplugpass enabled' in '[2024-11-14 06:47:12,999] DEBUG: Searching for testplugpass\nfound testplugpass in source: tests/data/recklessrepo/lightningd/testplugpass\n[2024-11-14 06:47:13,031] DEBUG: entry: testplugpass.py\n[2024-11-14 06:47:13,031] DEBUG: Retrieving testplugpass from tests/data/recklessrepo/lightningd/testplugpass\n[2024-11-14 06:47:13,032] DEBUG: Install requested from InstInfo(testplugpass, tests/data/recklessrepo/lightningd/testplugpass, None, testplugpass.py, requirements.txt, None).\n[2024-11-14 06:47:13,032] DEBUG: copying local directory contents from tests/data/recklessrepo/lightningd/testplugpass\n[2024-11-14 06:47:13,038] DEBUG: cloned_src: InstInfo(testplugpass, /tmp/reckless-0721411112afw6ng7/clone, None, testplugpass.py, requirements.txt, testplugpass)\n[2024-11-14 06:47:13,038] DEBUG: using installer python3venv\n[2024-11-14 06:47:13,038] DEBUG: creating /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass\n[2024-11-14 06:47:13,038] DEBUG: creating /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/source\n[2024-11-14 06:47:13,038] DEBUG: copying /tmp/reckless-0721411112afw6ng7/clone/testplugpass tree to /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/source/testplugpass\n[2024-11-14 06:47:13,039] DEBUG: linking source /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/source/testplugpass/testplugpass.py to /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/testplugpass.py\n[2024-11-14 06:47:13,039] DEBUG: InstInfo(testplugpass, /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass, None, testplugpass.py, requirements.txt, source/testplugpass)\n[2024-11-14 06:47:20,508] DEBUG: configuring a python virtual environment (pip) in /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/.venv\n[2024-11-14 06:47:20,508] DEBUG: virtual environment created in /tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/.venv.\nCollecting pyln-client\n  Using cached pyln_client-24.8.2-py3-none-any.whl (35 kB)\nCollecting pyln-proto>=23\n  Using cached pyln_proto-24.8.2-py3-none-any.whl (31 kB)\nCollecting pyln-bolt7>=1.0\n  Using cached pyln_bolt7-1.0.246-py3-none-any.whl (18 kB)\nCollecting base58<3.0.0,>=2.1.1\n  Using cached base58-2.1.1-py3-none-any.whl (5.6 kB)\nCollecting cryptography<43,>=42\n  Using cached cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl (3.9 MB)\nCollecting bitstring<5.0.0,>=4.1.0\n  Using cached bitstring-4.2.3-py3-none-any.whl (71 kB)\nCollecting coincurve<21,>=20\n  Using cached coincurve-20.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.2 MB)\nCollecting PySocks<2,>=1\n  Using cached PySocks-1.7.1-py3-none-any.whl (16 kB)\nCollecting bitarray<3.0.0,>=2.9.0\n  Using cached bitarray-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (288 kB)\nCollecting cffi>=1.3.0\n  Using cached cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (446 kB)\nCollecting asn1crypto\n  Using cached asn1crypto-1.5.1-py2.py3-none-any.whl (105 kB)\nCollecting pycparser\n  Using cached pycparser-2.22-py3-none-any.whl (117 kB)\nInstalling collected packages: bitarray, asn1crypto, PySocks, pyln-bolt7, pycparser, bitstring, base58, cffi, cryptography, coincurve, pyln-proto, pyln-client\nSuccessfully installed PySocks-1.7.1 asn1crypto-1.5.1 base58-2.1.1 bitarray-2.9.3 bitstring-4.2.3 cffi-1.17.1 coincurve-20.0.0 cryptography-42.0.8 pycparser-2.22 pyln-bolt7-1.0.246 pyln-client-24.8.2 pyln-proto-24.8.2\ndependencies installed successfully\n[2024-11-14 06:47:37,424] DEBUG: virtual environment for cloned plugin: .venv\n[2024-11-14 06:47:37,746] DEBUG: plugin testing error:\n[2024-11-14 06:47:37,746] DEBUG:   Traceback (most recent call last):\n[2024-11-14 06:47:37,746] DEBUG:     File "/tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/testplugpass.py", line 9, in <module>\n[2024-11-14 06:47:37,746] DEBUG:       runpy.run_module("testplugpass", {}, "__main__")\n[2024-11-14 06:47:37,746] DEBUG:     File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/runpy.py", line 210, in run_module\n[2024-11-14 06:47:37,746] DEBUG:       return _run_code(code, {}, init_globals, run_name, mod_spec)\n[2024-11-14 06:47:37,746] DEBUG:     File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/runpy.py", line 87, in _run_code\n[2024-11-14 06:47:37,746] DEBUG:       exec(code, run_globals)\n[2024-11-14 06:47:37,746] DEBUG:     File "/tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/source/testplugpass/testplugpass.py", line 25, in <module>\n[2024-11-14 06:47:37,746] DEBUG:       plugin.run()\n[2024-11-14 06:47:37,746] DEBUG:     File "/tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/.venv/lib/python3.8/site-packages/pyln/client/plugin.py", line 877, in run\n[2024-11-14 06:47:37,747] DEBUG:       return self.print_usage()\n[2024-11-14 06:47:37,747] DEBUG:     File "/tmp/ltests-chy6ayqu/test_local_dir_install_2/lightning-1/reckless/testplugpass/.venv/lib/python3.8/site-packages/pyln/client/plugin.py", line 831, in print_usage\n[2024-11-14 06:47:37,747] DEBUG:       doc = method.long_desc if method.long_desc is not None else "No documentation found"\n[2024-11-14 06:47:37,747] DEBUG:   AttributeError: \'Method\' object has no attribute \'long_desc\'\n[2024-11-14 06:47:37,747] ERROR: plugin testing failed\n[2024-11-14 06:47:37,937] WARNING: testplugpass: installation aborted\n'
```

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2024-11-17 14:09:10 +10:30

317 lines
13 KiB
Python

from fixtures import * # noqa: F401,F403
import subprocess
from pathlib import PosixPath, Path
import socket
from pyln.testing.utils import VALGRIND
import pytest
import os
import shutil
import time
import unittest
@pytest.fixture(autouse=True)
def canned_github_server(directory):
global NETWORK
NETWORK = os.environ.get('TEST_NETWORK')
if NETWORK is None:
NETWORK = 'regtest'
FILE_PATH = Path(os.path.dirname(os.path.realpath(__file__)))
if os.environ.get('LIGHTNING_CLI') is None:
os.environ['LIGHTNING_CLI'] = str(FILE_PATH.parent / 'cli/lightning-cli')
print('LIGHTNING_CALL: ', os.environ.get('LIGHTNING_CLI'))
# Use socket to provision a random free port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 0))
free_port = str(sock.getsockname()[1])
sock.close()
global my_env
my_env = os.environ.copy()
# This tells reckless to redirect to the canned server rather than github.
my_env['REDIR_GITHUB_API'] = f'http://127.0.0.1:{free_port}/api'
my_env['REDIR_GITHUB'] = directory
my_env['FLASK_RUN_PORT'] = free_port
my_env['FLASK_APP'] = str(FILE_PATH / 'rkls_github_canned_server')
server = subprocess.Popen(["python3", "-m", "flask", "run"],
env=my_env)
# Generate test plugin repository to test reckless against.
repo_dir = os.path.join(directory, "lightningd")
os.mkdir(repo_dir, 0o777)
plugins_path = str(FILE_PATH / 'data/recklessrepo/lightningd')
# Create requirements.txt file for the testpluginpass
# with pyln-client installed from the local source
requirements_file_path = os.path.join(plugins_path, 'testplugpass', 'requirements.txt')
with open(requirements_file_path, 'w') as f:
pyln_client_path = os.path.abspath(os.path.join(FILE_PATH, '..', 'contrib', 'pyln-client'))
f.write(f"pyln-client @ file://{pyln_client_path}\n")
# This lets us temporarily set .gitconfig user info in order to commit
my_env['HOME'] = directory
with open(os.path.join(directory, '.gitconfig'), 'w') as conf:
conf.write(("[user]\n"
"\temail = reckless@example.com\n"
"\tname = reckless CI\n"
"\t[init]\n"
"\tdefaultBranch = master"))
with open(os.path.join(directory, '.gitconfig'), 'r') as conf:
print(conf.readlines())
# Bare repository must be initialized prior to setting other git env vars
subprocess.check_output(['git', 'init', '--bare', 'plugins'], cwd=repo_dir,
env=my_env)
my_env['GIT_DIR'] = os.path.join(repo_dir, 'plugins')
my_env['GIT_WORK_TREE'] = repo_dir
my_env['GIT_INDEX_FILE'] = os.path.join(repo_dir, 'scratch-index')
repo_initialization = (f'cp -r {plugins_path}/* .;'
'git add --all;'
'git commit -m "initial commit - autogenerated by test_reckless.py";')
tag_and_update = ('git tag v1;'
"sed -i 's/v1/v2/g' testplugpass/testplugpass.py;"
'git add testplugpass/testplugpass.py;'
'git commit -m "update to v2";'
'git tag v2;')
subprocess.check_output([repo_initialization], env=my_env, shell=True,
cwd=repo_dir)
subprocess.check_output([tag_and_update], env=my_env,
shell=True, cwd=repo_dir)
del my_env['HOME']
del my_env['GIT_DIR']
del my_env['GIT_WORK_TREE']
del my_env['GIT_INDEX_FILE']
# We also need the github api data for the repo which will be served via http
shutil.copyfile(str(FILE_PATH / 'data/recklessrepo/rkls_api_lightningd_plugins.json'), os.path.join(directory, 'rkls_api_lightningd_plugins.json'))
yield
# Delete requirements.txt from the testplugpass directory
with open(requirements_file_path, 'w') as f:
f.write(f"pyln-client\n\n")
server.terminate()
def reckless(cmds: list, dir: PosixPath = None,
autoconfirm=True, timeout: int = 15):
'''Call the reckless executable, optionally with a directory.'''
if dir is not None:
cmds.insert(0, "-l")
cmds.insert(1, str(dir))
cmds.insert(0, "tools/reckless")
r = subprocess.run(cmds, capture_output=True, encoding='utf-8', env=my_env,
input='Y\n')
print(" ".join(r.args), "\n")
print("***RECKLESS STDOUT***")
for l in r.stdout.splitlines():
print(l)
print('\n')
print("***RECKLESS STDERR***")
for l in r.stderr.splitlines():
print(l)
print('\n')
return r
def get_reckless_node(node_factory):
'''This may be unnecessary, but a preconfigured lightning dir
is useful for reckless testing.'''
node = node_factory.get_node(options={}, start=False)
return node
def check_stderr(stderr):
def output_okay(out):
for warning in ['[notice]', 'WARNING:', 'npm WARN',
'npm notice', 'DEPRECATION:', 'Creating virtualenv',
'config file not found:', 'press [Y]']:
if out.startswith(warning):
return True
return False
for e in stderr.splitlines():
if len(e) < 1:
continue
# Don't err on verbosity from pip, npm
assert output_okay(e)
def test_basic_help():
'''Validate that argparse provides basic help info.
This requires no config options passed to reckless.'''
r = reckless(["-h"])
assert r.returncode == 0
assert "positional arguments:" in r.stdout.splitlines()
assert "options:" in r.stdout.splitlines() or "optional arguments:" in r.stdout.splitlines()
def test_contextual_help(node_factory):
n = get_reckless_node(node_factory)
for subcmd in ['install', 'uninstall', 'search',
'enable', 'disable', 'source']:
r = reckless([subcmd, "-h"], dir=n.lightning_dir)
assert r.returncode == 0
assert "positional arguments:" in r.stdout.splitlines()
def test_sources(node_factory):
"""add additional sources and search through them"""
n = get_reckless_node(node_factory)
r = reckless(["source", "-h"], dir=n.lightning_dir)
assert r.returncode == 0
r = reckless(["source", "list"], dir=n.lightning_dir)
print(r.stdout)
assert r.returncode == 0
print(n.lightning_dir)
reckless_dir = Path(n.lightning_dir) / 'reckless'
print(dir(reckless_dir))
assert (reckless_dir / '.sources').exists()
print(os.listdir(reckless_dir))
print(reckless_dir / '.sources')
r = reckless([f"--network={NETWORK}", "-v", "source", "add",
"tests/data/recklessrepo/lightningd/testplugfail"],
dir=n.lightning_dir)
r = reckless([f"--network={NETWORK}", "-v", "source", "add",
"tests/data/recklessrepo/lightningd/testplugpass"],
dir=n.lightning_dir)
with open(reckless_dir / '.sources') as sources:
contents = [c.strip() for c in sources.readlines()]
print('contents:', contents)
assert 'https://github.com/lightningd/plugins' in contents
assert "tests/data/recklessrepo/lightningd/testplugfail" in contents
assert "tests/data/recklessrepo/lightningd/testplugpass" in contents
r = reckless([f"--network={NETWORK}", "-v", "source", "remove",
"tests/data/recklessrepo/lightningd/testplugfail"],
dir=n.lightning_dir)
with open(reckless_dir / '.sources') as sources:
contents = [c.strip() for c in sources.readlines()]
print('contents:', contents)
assert "tests/data/recklessrepo/lightningd/testplugfail" not in contents
assert "tests/data/recklessrepo/lightningd/testplugpass" in contents
def test_search(node_factory):
"""add additional sources and search through them"""
n = get_reckless_node(node_factory)
r = reckless([f"--network={NETWORK}", "search", "testplugpass"], dir=n.lightning_dir)
assert r.returncode == 0
assert 'found testplugpass in source: https://github.com/lightningd/plugins' in r.stdout
def test_install(node_factory):
"""test search, git clone, and installation to folder."""
n = get_reckless_node(node_factory)
r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass"], dir=n.lightning_dir)
assert r.returncode == 0
assert 'dependencies installed successfully' in r.stdout
assert 'plugin installed:' in r.stdout
assert 'testplugpass enabled' in r.stdout
check_stderr(r.stderr)
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass'
print(plugin_path)
assert os.path.exists(plugin_path)
@unittest.skipIf(VALGRIND, "virtual environment triggers memleak detection")
def test_poetry_install(node_factory):
"""test search, git clone, and installation to folder."""
n = get_reckless_node(node_factory)
r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpyproj"], dir=n.lightning_dir)
assert r.returncode == 0
assert 'dependencies installed successfully' in r.stdout
assert 'plugin installed:' in r.stdout
assert 'testplugpyproj enabled' in r.stdout
check_stderr(r.stderr)
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpyproj'
print(plugin_path)
assert os.path.exists(plugin_path)
n.start()
print(n.rpc.testmethod())
assert n.daemon.is_in_log(r'plugin-manager: started\([0-9].*\) /tmp/ltests-[a-z0-9_].*/test_poetry_install_1/lightning-1/reckless/testplugpyproj/testplugpyproj.py')
assert n.rpc.testmethod() == 'I live.'
@unittest.skipIf(VALGRIND, "virtual environment triggers memleak detection")
@unittest.skip("Broken")
def test_local_dir_install(node_factory):
"""Test search and install from local directory source."""
n = get_reckless_node(node_factory)
n.start()
r = reckless([f"--network={NETWORK}", "-v", "source", "add",
"tests/data/recklessrepo/lightningd/testplugpass"],
dir=n.lightning_dir)
assert r.returncode == 0
r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass"], dir=n.lightning_dir)
assert r.returncode == 0
assert 'testplugpass enabled' in r.stdout
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass'
print(plugin_path)
assert os.path.exists(plugin_path)
@unittest.skipIf(VALGRIND, "virtual environment triggers memleak detection")
def test_disable_enable(node_factory):
"""test search, git clone, and installation to folder."""
n = get_reckless_node(node_factory)
# Test case-insensitive search as well
r = reckless([f"--network={NETWORK}", "-v", "install", "testPlugPass"],
dir=n.lightning_dir)
assert r.returncode == 0
assert 'dependencies installed successfully' in r.stdout
assert 'plugin installed:' in r.stdout
assert 'testplugpass enabled' in r.stdout
check_stderr(r.stderr)
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass'
print(plugin_path)
assert os.path.exists(plugin_path)
r = reckless([f"--network={NETWORK}", "-v", "disable", "testPlugPass"],
dir=n.lightning_dir)
assert r.returncode == 0
n.start()
# Should find it with or without the file extension
r = reckless([f"--network={NETWORK}", "-v", "enable", "testplugpass.py"],
dir=n.lightning_dir)
assert r.returncode == 0
assert 'testplugpass enabled' in r.stdout
test_plugin = {'name': str(plugin_path / 'testplugpass.py'),
'active': True, 'dynamic': True}
time.sleep(1)
print(n.rpc.plugin_list()['plugins'])
assert test_plugin in n.rpc.plugin_list()['plugins']
@unittest.skipIf(VALGRIND, "virtual environment triggers memleak detection")
def test_tag_install(node_factory):
"install a plugin from a specific commit hash or tag"
node = get_reckless_node(node_factory)
node.start()
r = reckless([f"--network={NETWORK}", "-v", "install", "testPlugPass"],
dir=node.lightning_dir)
assert r.returncode == 0
metadata = node.lightning_dir / "reckless/testplugpass/.metadata"
with open(metadata, "r") as md:
header = ''
for line in md.readlines():
line = line.strip()
if header == 'requested commit':
assert line == 'None'
header = line
# should install v2 (latest) without specifying
version = node.rpc.gettestplugversion()
assert version == 'v2'
r = reckless([f"--network={NETWORK}", "-v", "uninstall", "testplugpass"],
dir=node.lightning_dir)
r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass@v1"],
dir=node.lightning_dir)
assert r.returncode == 0
# v1 should now be checked out.
version = node.rpc.gettestplugversion()
assert version == 'v1'
installed_path = Path(node.lightning_dir) / 'reckless/testplugpass'
assert installed_path.is_dir()
with open(metadata, "r") as md:
header = ''
for line in md.readlines():
line = line.strip()
if header == 'requested commit':
assert line == 'v1'
header = line