mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
pytest: add blackbox tests for reckless
A canned lightningd/plugins is used to test against. This allows faster and more deterministic outcomes. Changelog-None
This commit is contained in:
parent
cf203369bc
commit
2f050621b0
8
tests/data/recklessrepo/lightningd/.gitignore
vendored
Normal file
8
tests/data/recklessrepo/lightningd/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
*.pyc
|
||||
*.tmp
|
||||
.mypy_cache
|
||||
TAGS
|
||||
tags
|
||||
.pytest_cache
|
||||
__pycache__
|
||||
|
3
tests/data/recklessrepo/lightningd/testplugfail/testplugfail.py
Executable file
3
tests/data/recklessrepo/lightningd/testplugfail/testplugfail.py
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
print("We don't need no stinkin manifest")
|
17
tests/data/recklessrepo/lightningd/testplugpass/testplugpass.py
Executable file
17
tests/data/recklessrepo/lightningd/testplugpass/testplugpass.py
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
from pyln.client import Plugin
|
||||
|
||||
plugin = Plugin()
|
||||
|
||||
|
||||
@plugin.init()
|
||||
def init(options, configuration, plugin, **kwargs):
|
||||
plugin.log("testplug initialized")
|
||||
|
||||
|
||||
@plugin.method("testmethod")
|
||||
def testmethod(plugin):
|
||||
return ("I live.")
|
||||
|
||||
|
||||
plugin.run()
|
20
tests/data/recklessrepo/rkls_api_lightningd_plugins.json
Normal file
20
tests/data/recklessrepo/rkls_api_lightningd_plugins.json
Normal file
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"name": "testplugpass",
|
||||
"path": "testplugpass",
|
||||
"url": "https://api.github.com/repos/lightningd/plugins/contents/webhook?ref=master",
|
||||
"html_url": "https://github.com/lightningd/plugins/tree/master/testplugpass",
|
||||
"git_url": "https://api.github.com/repos/lightningd/plugins/git/trees/testplugpass",
|
||||
"download_url": null,
|
||||
"type": "dir"
|
||||
},
|
||||
{
|
||||
"name": "testplugfail",
|
||||
"path": "testplugfail",
|
||||
"url": "https://api.github.com/repos/lightningd/plugins/contents/testplugfail?ref=master",
|
||||
"html_url": "https://github.com/lightningd/plugins/tree/master/testplugfail",
|
||||
"git_url": "https://api.github.com/repos/lightningd/plugins/git/trees/testplugfail",
|
||||
"download_url": null,
|
||||
"type": "dir"
|
||||
}
|
||||
]
|
42
tests/rkls_github_canned_server.py
Normal file
42
tests/rkls_github_canned_server.py
Normal file
@ -0,0 +1,42 @@
|
||||
import flask
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def create_app(test_config=None):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route("/api/repos/<github_user>/<github_repo>/contents/")
|
||||
def github_plugins_repo_api(github_user, github_repo):
|
||||
'''This emulates api.github.com calls to lightningd/plugins'''
|
||||
user = flask.escape(github_user)
|
||||
repo = flask.escape(github_repo)
|
||||
canned_api = os.environ.get('REDIR_GITHUB') + f'/rkls_api_{user}_{repo}.json'
|
||||
with open(canned_api, 'rb') as f:
|
||||
canned_data = f.read(-1)
|
||||
print(f'serving canned api data from {canned_api}')
|
||||
resp = flask.Response(response=canned_data,
|
||||
headers={'Content-Type': 'application/json; charset=utf-8'})
|
||||
return resp
|
||||
|
||||
@app.route("/api/repos/<github_user>/<github_repo>/git/trees/<plugin_name>")
|
||||
def github_plugin_tree_api(github_user, github_repo, plugin_name):
|
||||
dir_json = \
|
||||
{
|
||||
"url": f"https://api.github.com/repos/{github_user}/{github_repo}/git/trees/{plugin_name}",
|
||||
"tree": []
|
||||
}
|
||||
# FIXME: Pull contents from directory
|
||||
for file in os.listdir(f'tests/data/recklessrepo/{github_user}/{plugin_name}'):
|
||||
dir_json["tree"].append({"path": file})
|
||||
resp = flask.Response(response=json.dumps(dir_json),
|
||||
headers={'Content-Type': 'application/json; charset=utf-8'})
|
||||
return resp
|
||||
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
app.run(debug=True)
|
188
tests/test_reckless.py
Normal file
188
tests/test_reckless.py
Normal file
@ -0,0 +1,188 @@
|
||||
from fixtures import * # noqa: F401,F403
|
||||
import subprocess
|
||||
from pathlib import PosixPath, Path
|
||||
import socket
|
||||
import pytest
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
|
||||
@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')
|
||||
# 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";')
|
||||
subprocess.check_output([repo_initialization], 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
|
||||
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]', 'npm WARN', 'npm notice']:
|
||||
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
|
||||
|
||||
|
||||
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 repo: 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)
|
||||
|
||||
|
||||
def test_disable_enable(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)
|
||||
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.py 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'])
|
Loading…
Reference in New Issue
Block a user