2023-04-05 19:48:23 -05:00
|
|
|
from fixtures import * # noqa: F401,F403
|
|
|
|
|
import subprocess
|
|
|
|
|
from pathlib import PosixPath, Path
|
|
|
|
|
import socket
|
2026-01-07 09:43:11 +10:30
|
|
|
from pyln.testing.utils import VALGRIND
|
2023-04-05 19:48:23 -05:00
|
|
|
import pytest
|
|
|
|
|
import os
|
2025-08-11 13:44:07 -05:00
|
|
|
import re
|
2023-04-05 19:48:23 -05:00
|
|
|
import shutil
|
|
|
|
|
import time
|
2024-02-02 16:43:11 -06:00
|
|
|
import unittest
|
2023-04-05 19:48:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@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')
|
2024-09-03 20:29:47 -07:00
|
|
|
|
|
|
|
|
# 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")
|
|
|
|
|
|
2023-04-05 19:48:23 -05:00
|
|
|
# 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";')
|
2024-01-26 12:32:47 -06:00
|
|
|
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;')
|
2023-04-05 19:48:23 -05:00
|
|
|
subprocess.check_output([repo_initialization], env=my_env, shell=True,
|
|
|
|
|
cwd=repo_dir)
|
2024-01-26 12:32:47 -06:00
|
|
|
subprocess.check_output([tag_and_update], env=my_env,
|
|
|
|
|
shell=True, cwd=repo_dir)
|
2023-04-05 19:48:23 -05:00
|
|
|
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
|
2024-09-03 20:29:47 -07:00
|
|
|
# Delete requirements.txt from the testplugpass directory
|
|
|
|
|
with open(requirements_file_path, 'w') as f:
|
|
|
|
|
f.write(f"pyln-client\n\n")
|
2023-04-05 19:48:23 -05:00
|
|
|
server.terminate()
|
|
|
|
|
|
|
|
|
|
|
2025-08-11 13:44:07 -05:00
|
|
|
class RecklessResult:
|
|
|
|
|
def __init__(self, process, returncode, stdout, stderr):
|
|
|
|
|
self.process = process
|
|
|
|
|
self.returncode = returncode
|
|
|
|
|
self.stdout = stdout
|
|
|
|
|
self.stderr = stderr
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return f'self.returncode, self.stdout, self.stderr'
|
|
|
|
|
|
|
|
|
|
def search_stdout(self, regex):
|
|
|
|
|
"""return the matching regex line from reckless output."""
|
|
|
|
|
ex = re.compile(regex)
|
|
|
|
|
matching = []
|
|
|
|
|
for line in self.stdout:
|
|
|
|
|
if ex.search(line):
|
|
|
|
|
matching.append(line)
|
|
|
|
|
return matching
|
|
|
|
|
|
2025-08-11 14:08:49 -05:00
|
|
|
def check_stderr(self):
|
|
|
|
|
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 self.stderr:
|
|
|
|
|
if len(e) < 1:
|
|
|
|
|
continue
|
|
|
|
|
# Don't err on verbosity from pip, npm
|
|
|
|
|
if not output_okay(e):
|
|
|
|
|
raise Exception(f'reckless stderr contains `{e}`')
|
|
|
|
|
|
2025-08-11 13:44:07 -05:00
|
|
|
|
2023-04-05 19:48:23 -05:00
|
|
|
def reckless(cmds: list, dir: PosixPath = None,
|
2025-08-11 13:44:07 -05:00
|
|
|
autoconfirm=True, timeout: int = 60):
|
2023-04-05 19:48:23 -05:00
|
|
|
'''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")
|
2025-08-11 13:44:07 -05:00
|
|
|
if autoconfirm:
|
|
|
|
|
process_input = 'Y\n'
|
|
|
|
|
else:
|
|
|
|
|
process_input = None
|
2023-04-05 19:48:23 -05:00
|
|
|
r = subprocess.run(cmds, capture_output=True, encoding='utf-8', env=my_env,
|
2025-08-11 13:44:07 -05:00
|
|
|
input=process_input, timeout=timeout)
|
|
|
|
|
stdout = r.stdout.splitlines()
|
|
|
|
|
stderr = r.stderr.splitlines()
|
2023-04-05 19:48:23 -05:00
|
|
|
print(" ".join(r.args), "\n")
|
|
|
|
|
print("***RECKLESS STDOUT***")
|
2025-08-11 13:44:07 -05:00
|
|
|
for l in stdout:
|
2023-04-05 19:48:23 -05:00
|
|
|
print(l)
|
|
|
|
|
print('\n')
|
|
|
|
|
print("***RECKLESS STDERR***")
|
2025-08-11 13:44:07 -05:00
|
|
|
for l in stderr:
|
2023-04-05 19:48:23 -05:00
|
|
|
print(l)
|
|
|
|
|
print('\n')
|
2025-08-11 13:44:07 -05:00
|
|
|
return RecklessResult(r, r.returncode, stdout, stderr)
|
2023-04-05 19:48:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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 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
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout("positional arguments:")
|
|
|
|
|
assert r.search_stdout("options:") or r.search_stdout("optional arguments:")
|
2023-04-05 19:48:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout("positional arguments:")
|
2023-04-05 19:48:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2023-11-13 15:58:54 -06:00
|
|
|
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')
|
2024-11-15 21:11:24 -08:00
|
|
|
plugin_dir = str(os.path.join(n.lightning_dir, '..', 'lightningd'))
|
2023-11-13 15:58:54 -06:00
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "source", "add",
|
2024-11-15 21:11:24 -08:00
|
|
|
f'{plugin_dir}/testplugfail'],
|
2023-11-13 15:58:54 -06:00
|
|
|
dir=n.lightning_dir)
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "source", "add",
|
2024-11-15 21:11:24 -08:00
|
|
|
f'{plugin_dir}/testplugpass'],
|
2023-11-13 15:58:54 -06:00
|
|
|
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
|
2024-11-15 21:11:24 -08:00
|
|
|
assert f'{plugin_dir}/testplugfail' in contents
|
|
|
|
|
assert f'{plugin_dir}/testplugpass' in contents
|
2023-11-13 15:58:54 -06:00
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "source", "remove",
|
2024-11-15 21:11:24 -08:00
|
|
|
f'{plugin_dir}/testplugfail'],
|
2023-11-13 15:58:54 -06:00
|
|
|
dir=n.lightning_dir)
|
|
|
|
|
with open(reckless_dir / '.sources') as sources:
|
|
|
|
|
contents = [c.strip() for c in sources.readlines()]
|
|
|
|
|
print('contents:', contents)
|
2024-11-15 21:11:24 -08:00
|
|
|
assert f'{plugin_dir}/testplugfail' not in contents
|
|
|
|
|
assert f'{plugin_dir}/testplugpass' in contents
|
2023-04-05 19:48:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('found testplugpass in source: https://github.com/lightningd/plugins')
|
2023-04-05 19:48:23 -05:00
|
|
|
|
|
|
|
|
|
2026-01-26 17:29:12 -05:00
|
|
|
def test_search_partial_match(node_factory):
|
|
|
|
|
"""test that partial/substring search returns multiple matches"""
|
|
|
|
|
n = get_reckless_node(node_factory)
|
|
|
|
|
|
|
|
|
|
# Search for partial name "testplug" - should find all test plugins
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "search", "testplug"], dir=n.lightning_dir)
|
|
|
|
|
# Should show the "Plugins matching" header
|
|
|
|
|
assert r.search_stdout("Plugins matching 'testplug':")
|
|
|
|
|
# Should list multiple plugins (all start with "testplug")
|
|
|
|
|
assert r.search_stdout('testplugpass')
|
|
|
|
|
assert r.search_stdout('testplugfail')
|
|
|
|
|
assert r.search_stdout('testplugpyproj')
|
|
|
|
|
assert r.search_stdout('testpluguv')
|
|
|
|
|
|
|
|
|
|
# Search for "pass" - should find testplugpass
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "search", "pass"], dir=n.lightning_dir)
|
|
|
|
|
assert r.search_stdout("Plugins matching 'pass':")
|
|
|
|
|
assert r.search_stdout('testplugpass')
|
|
|
|
|
# Should not find plugins without "pass" in name
|
|
|
|
|
assert not r.search_stdout('testplugfail')
|
|
|
|
|
|
|
|
|
|
# Search for something that doesn't exist
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "search", "nonexistent"], dir=n.lightning_dir)
|
|
|
|
|
assert r.search_stdout("Search exhausted all sources")
|
|
|
|
|
|
|
|
|
|
|
2023-04-05 19:48:23 -05:00
|
|
|
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
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('dependencies installed successfully')
|
|
|
|
|
assert r.search_stdout('plugin installed:')
|
|
|
|
|
assert r.search_stdout('testplugpass enabled')
|
2025-08-11 14:08:49 -05:00
|
|
|
r.check_stderr()
|
2023-04-05 19:48:23 -05:00
|
|
|
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass'
|
|
|
|
|
print(plugin_path)
|
|
|
|
|
assert os.path.exists(plugin_path)
|
|
|
|
|
|
|
|
|
|
|
2024-01-26 12:32:47 -06:00
|
|
|
@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
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('dependencies installed successfully')
|
|
|
|
|
assert r.search_stdout('plugin installed:')
|
|
|
|
|
assert r.search_stdout('testplugpyproj enabled')
|
2025-08-11 14:08:49 -05:00
|
|
|
r.check_stderr()
|
2024-01-26 12:32:47 -06:00
|
|
|
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.'
|
|
|
|
|
|
|
|
|
|
|
2024-02-02 16:43:11 -06:00
|
|
|
@unittest.skipIf(VALGRIND, "virtual environment triggers memleak detection")
|
2023-07-25 14:54:21 -05:00
|
|
|
def test_local_dir_install(node_factory):
|
|
|
|
|
"""Test search and install from local directory source."""
|
|
|
|
|
n = get_reckless_node(node_factory)
|
|
|
|
|
n.start()
|
2025-05-13 12:06:10 -05:00
|
|
|
source_dir = str(Path(n.lightning_dir / '..' / 'lightningd' / 'testplugpass').resolve())
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "source", "add", source_dir], dir=n.lightning_dir)
|
2023-07-25 14:54:21 -05:00
|
|
|
assert r.returncode == 0
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass"], dir=n.lightning_dir)
|
|
|
|
|
assert r.returncode == 0
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('testplugpass enabled')
|
2023-07-25 14:54:21 -05:00
|
|
|
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass'
|
|
|
|
|
print(plugin_path)
|
|
|
|
|
assert os.path.exists(plugin_path)
|
|
|
|
|
|
2025-05-13 12:06:10 -05:00
|
|
|
# Retry with a direct install passing the full path to the local source
|
|
|
|
|
r = reckless(['uninstall', 'testplugpass', '-v'], dir=n.lightning_dir)
|
|
|
|
|
assert not os.path.exists(plugin_path)
|
|
|
|
|
r = reckless(['source', 'remove', source_dir], dir=n.lightning_dir)
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('plugin source removed')
|
2025-05-13 12:06:10 -05:00
|
|
|
r = reckless(['install', '-v', source_dir], dir=n.lightning_dir)
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('testplugpass enabled')
|
2025-05-13 12:06:10 -05:00
|
|
|
assert os.path.exists(plugin_path)
|
|
|
|
|
|
2023-07-25 14:54:21 -05:00
|
|
|
|
2024-02-02 16:43:11 -06:00
|
|
|
@unittest.skipIf(VALGRIND, "virtual environment triggers memleak detection")
|
2023-04-05 19:48:23 -05:00
|
|
|
def test_disable_enable(node_factory):
|
|
|
|
|
"""test search, git clone, and installation to folder."""
|
|
|
|
|
n = get_reckless_node(node_factory)
|
2023-04-12 13:14:00 -05:00
|
|
|
# Test case-insensitive search as well
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "install", "testPlugPass"],
|
2023-04-05 19:48:23 -05:00
|
|
|
dir=n.lightning_dir)
|
|
|
|
|
assert r.returncode == 0
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('dependencies installed successfully')
|
|
|
|
|
assert r.search_stdout('plugin installed:')
|
|
|
|
|
assert r.search_stdout('testplugpass enabled')
|
2025-08-11 14:08:49 -05:00
|
|
|
r.check_stderr()
|
2023-04-05 19:48:23 -05:00
|
|
|
plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass'
|
|
|
|
|
print(plugin_path)
|
|
|
|
|
assert os.path.exists(plugin_path)
|
2023-04-12 13:14:00 -05:00
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "disable", "testPlugPass"],
|
2023-04-05 19:48:23 -05:00
|
|
|
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
|
2025-08-11 13:44:07 -05:00
|
|
|
assert r.search_stdout('testplugpass enabled')
|
2023-04-05 19:48:23 -05:00
|
|
|
test_plugin = {'name': str(plugin_path / 'testplugpass.py'),
|
|
|
|
|
'active': True, 'dynamic': True}
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
print(n.rpc.plugin_list()['plugins'])
|
2024-01-26 12:32:47 -06:00
|
|
|
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
|
2025-08-11 14:14:25 -05:00
|
|
|
|
|
|
|
|
|
pytest: mark reckless install test flaky.
Sometimes it times out under CI, but running 100 times here reveals
nothing. Assume network issues and mark it flaky.
```
node_factory = <pyln.testing.utils.NodeFactory object at 0x7f6e700b8970>
@pytest.mark.slow_test
def test_reckless_uv_install(node_factory):
node = get_reckless_node(node_factory)
node.start()
> r = reckless([f"--network={NETWORK}", "-v", "install", "testpluguv"],
dir=node.lightning_dir)
tests/test_reckless.py:358:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_reckless.py:141: in reckless
r = subprocess.run(cmds, capture_output=True, encoding='utf-8', env=my_env,
/opt/hostedtoolcache/Python/3.10.19/x64/lib/python3.10/subprocess.py:505: in run
stdout, stderr = process.communicate(input, timeout=timeout)
/opt/hostedtoolcache/Python/3.10.19/x64/lib/python3.10/subprocess.py:1154: in communicate
stdout, stderr = self._communicate(input, endtime, timeout)
/opt/hostedtoolcache/Python/3.10.19/x64/lib/python3.10/subprocess.py:2022: in _communicate
self._check_timeout(endtime, orig_timeout, stdout, stderr)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Popen: returncode: -9 args: ['tools/reckless', '-l', '/tmp/ltests-kr4cjtd8/...>
endtime = 4623.246515403, orig_timeout = 60
stdout_seq = [b'[2026-01-07 05:55:15,159] DEBUG: Warning: Reckless requires write access\n[2026-01-07 05:55:15,159] DEBUG: Searching for testpluguv\n', b'[2026-01-07 05:55:15,179] DEBUG: InstInfo(testpluguv, https://github.com/lightningd/plugins, None, None, None, testpluguv), Source.GITHUB_REPO\nfound testpluguv in source: https://github.com/lightningd/plugins\n[2026-01-07 05:55:15,179] DEBUG: entry: None\n[2026-01-07 05:55:15,179] DEBUG: sub-directory: testpluguv\n[2026-01-07 05:55:15,179] DEBUG: Retrieving testpluguv from https://github.com/lightningd/plugins\n[2026-01-07 05:55:15,179] DEBUG: Install requested from InstInfo(testpluguv, https://github.com/lightningd/plugins, None, None, None, testpluguv).\n', b'cloning Source.GITHUB_REPO InstInfo(testpluguv, https://github.com/lightningd/plugins, None, None, None, testpluguv)\n[2026-01-07 05:55:15,405] DEBUG: cloned_src: InstInfo(testpluguv, /tmp/reckless-433081020a3dff932/clone, None, testpluguv.py, uv.lock, testpluguv/testpluguv)\n', b'[2026-01-07 05:55:15,409] DEBUG: using latest commit of default branch\n', b'[2026-01-07 05:55:15,417] DEBUG: checked out HEAD: 095457638f8080cd614a81cb4ad1cba7883549e3\n[2026-01-07 05:55:15,417] DEBUG: using installer pythonuv\n[2026-01-07 05:55:15,417] DEBUG: creating /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv\n[2026-01-07 05:55:15,418] DEBUG: creating /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/source\n[2026-01-07 05:55:15,418] DEBUG: copying /tmp/reckless-433081020a3dff932/clone/testpluguv/testpluguv tree to /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/source/testpluguv\n[2026-01-07 05:55:15,419] DEBUG: linking source /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/source/testpluguv/testpluguv.py to /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/testpluguv.py\n[2026-01-07 05:55:15,419] DEBUG: InstInfo(testpluguv, /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv, None, testpluguv.py, uv.lock, source/testpluguv)\n']
stderr_seq = [b'config file not found: /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/liquid-regtest/config\npress [Y] to create one now.\nconfig file not found: /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/liquid-regtest-reckless.conf\nconfig file not found: /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/.sources\n']
skip_check_and_raise = False
def _check_timeout(self, endtime, orig_timeout, stdout_seq, stderr_seq,
skip_check_and_raise=False):
"""Convenience for checking if a timeout has expired."""
if endtime is None:
return
if skip_check_and_raise or _time() > endtime:
> raise TimeoutExpired(
self.args, orig_timeout,
output=b''.join(stdout_seq) if stdout_seq else None,
stderr=b''.join(stderr_seq) if stderr_seq else None)
E subprocess.TimeoutExpired: Command '['tools/reckless', '-l', '/tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1', '--network=liquid-regtest', '-v', 'install', 'testpluguv']' timed out after 60 seconds
```
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2026-01-07 19:09:02 +10:30
|
|
|
# Note: uv timeouts from the GH network seem to happen?
|
2026-01-07 09:43:11 +10:30
|
|
|
@pytest.mark.slow_test
|
pytest: mark reckless install test flaky.
Sometimes it times out under CI, but running 100 times here reveals
nothing. Assume network issues and mark it flaky.
```
node_factory = <pyln.testing.utils.NodeFactory object at 0x7f6e700b8970>
@pytest.mark.slow_test
def test_reckless_uv_install(node_factory):
node = get_reckless_node(node_factory)
node.start()
> r = reckless([f"--network={NETWORK}", "-v", "install", "testpluguv"],
dir=node.lightning_dir)
tests/test_reckless.py:358:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_reckless.py:141: in reckless
r = subprocess.run(cmds, capture_output=True, encoding='utf-8', env=my_env,
/opt/hostedtoolcache/Python/3.10.19/x64/lib/python3.10/subprocess.py:505: in run
stdout, stderr = process.communicate(input, timeout=timeout)
/opt/hostedtoolcache/Python/3.10.19/x64/lib/python3.10/subprocess.py:1154: in communicate
stdout, stderr = self._communicate(input, endtime, timeout)
/opt/hostedtoolcache/Python/3.10.19/x64/lib/python3.10/subprocess.py:2022: in _communicate
self._check_timeout(endtime, orig_timeout, stdout, stderr)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Popen: returncode: -9 args: ['tools/reckless', '-l', '/tmp/ltests-kr4cjtd8/...>
endtime = 4623.246515403, orig_timeout = 60
stdout_seq = [b'[2026-01-07 05:55:15,159] DEBUG: Warning: Reckless requires write access\n[2026-01-07 05:55:15,159] DEBUG: Searching for testpluguv\n', b'[2026-01-07 05:55:15,179] DEBUG: InstInfo(testpluguv, https://github.com/lightningd/plugins, None, None, None, testpluguv), Source.GITHUB_REPO\nfound testpluguv in source: https://github.com/lightningd/plugins\n[2026-01-07 05:55:15,179] DEBUG: entry: None\n[2026-01-07 05:55:15,179] DEBUG: sub-directory: testpluguv\n[2026-01-07 05:55:15,179] DEBUG: Retrieving testpluguv from https://github.com/lightningd/plugins\n[2026-01-07 05:55:15,179] DEBUG: Install requested from InstInfo(testpluguv, https://github.com/lightningd/plugins, None, None, None, testpluguv).\n', b'cloning Source.GITHUB_REPO InstInfo(testpluguv, https://github.com/lightningd/plugins, None, None, None, testpluguv)\n[2026-01-07 05:55:15,405] DEBUG: cloned_src: InstInfo(testpluguv, /tmp/reckless-433081020a3dff932/clone, None, testpluguv.py, uv.lock, testpluguv/testpluguv)\n', b'[2026-01-07 05:55:15,409] DEBUG: using latest commit of default branch\n', b'[2026-01-07 05:55:15,417] DEBUG: checked out HEAD: 095457638f8080cd614a81cb4ad1cba7883549e3\n[2026-01-07 05:55:15,417] DEBUG: using installer pythonuv\n[2026-01-07 05:55:15,417] DEBUG: creating /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv\n[2026-01-07 05:55:15,418] DEBUG: creating /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/source\n[2026-01-07 05:55:15,418] DEBUG: copying /tmp/reckless-433081020a3dff932/clone/testpluguv/testpluguv tree to /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/source/testpluguv\n[2026-01-07 05:55:15,419] DEBUG: linking source /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/source/testpluguv/testpluguv.py to /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv/testpluguv.py\n[2026-01-07 05:55:15,419] DEBUG: InstInfo(testpluguv, /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/testpluguv, None, testpluguv.py, uv.lock, source/testpluguv)\n']
stderr_seq = [b'config file not found: /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/liquid-regtest/config\npress [Y] to create one now.\nconfig file not found: /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/liquid-regtest-reckless.conf\nconfig file not found: /tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1/reckless/.sources\n']
skip_check_and_raise = False
def _check_timeout(self, endtime, orig_timeout, stdout_seq, stderr_seq,
skip_check_and_raise=False):
"""Convenience for checking if a timeout has expired."""
if endtime is None:
return
if skip_check_and_raise or _time() > endtime:
> raise TimeoutExpired(
self.args, orig_timeout,
output=b''.join(stdout_seq) if stdout_seq else None,
stderr=b''.join(stderr_seq) if stderr_seq else None)
E subprocess.TimeoutExpired: Command '['tools/reckless', '-l', '/tmp/ltests-kr4cjtd8/test_reckless_uv_install_1/lightning-1', '--network=liquid-regtest', '-v', 'install', 'testpluguv']' timed out after 60 seconds
```
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2026-01-07 19:09:02 +10:30
|
|
|
@pytest.mark.flaky(reruns=3)
|
2025-08-11 14:14:25 -05:00
|
|
|
def test_reckless_uv_install(node_factory):
|
|
|
|
|
node = get_reckless_node(node_factory)
|
|
|
|
|
node.start()
|
|
|
|
|
r = reckless([f"--network={NETWORK}", "-v", "install", "testpluguv"],
|
|
|
|
|
dir=node.lightning_dir)
|
|
|
|
|
assert r.returncode == 0
|
|
|
|
|
installed_path = Path(node.lightning_dir) / 'reckless/testpluguv'
|
|
|
|
|
assert installed_path.is_dir()
|
|
|
|
|
assert node.rpc.uvplugintest() == 'I live.'
|
|
|
|
|
version = node.rpc.getuvpluginversion()
|
|
|
|
|
assert version == 'v1'
|
|
|
|
|
|
|
|
|
|
assert r.search_stdout('using installer pythonuv')
|
|
|
|
|
r.check_stderr()
|