Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CI: Add two columns for the main and nightly workflows to the "Available plugins" table in README.md #509

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 106 additions & 22 deletions .ci/test.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from pathlib import Path
import subprocess
from pprint import pprint
from collections import namedtuple
from typing import Generator

import json
import logging
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
import shlex
import os
from collections import namedtuple
from pathlib import Path, PosixPath
from typing import Generator, List
from itertools import chain

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

Expand All @@ -18,8 +18,8 @@
'.ci',
'.git',
'.github',
'archived',
'lightning',
'feeadjuster'
]

global_dependencies = [
Expand All @@ -42,6 +42,10 @@
)


def list_plugins(plugins):
return ", ".join([p.name for p in sorted(plugins)])


def enumerate_plugins(basedir: Path) -> Generator[Plugin, None, None]:
plugins = list([
x for x in basedir.iterdir() \
Expand All @@ -50,13 +54,17 @@ def enumerate_plugins(basedir: Path) -> Generator[Plugin, None, None]:
pip_pytest = [
x for x in plugins if (x / Path('requirements.txt')).exists()
]
print(f"Pip plugins: {list_plugins(pip_pytest)}")

poetry_pytest = [
x for x in plugins if (x / Path("pyproject.toml")).exists()
]
print(poetry_pytest)
print(f"Poetry plugins: {list_plugins(poetry_pytest)}")

other_plugins = [x for x in plugins if x not in pip_pytest and x not in poetry_pytest]
other_plugins = [
x for x in plugins if x not in pip_pytest and x not in poetry_pytest
]
print(f"Other plugins: {list_plugins(other_plugins)}")

for p in sorted(pip_pytest):
yield Plugin(
Expand Down Expand Up @@ -227,20 +235,24 @@ def install_pyln_testing(pip_path):
stderr=subprocess.STDOUT,
)

def run_one(p: Plugin) -> bool:
print("Running tests on plugin {p.name}".format(p=p))

testfiles = [
def get_testfiles(p: Plugin) -> List[PosixPath]:
return [
x for x in p.path.iterdir()
if (x.is_dir() and x.name == 'tests')
or (x.name.startswith("test_") and x.name.endswith('.py'))
]

if len(testfiles) == 0:
def has_testfiles(p: Plugin) -> bool:
return len(get_testfiles(p)) > 0

def run_one(p: Plugin) -> bool:
print("Running tests on plugin {p.name}".format(p=p))

if not has_testfiles(p):
print("No test files found, skipping plugin {p.name}".format(p=p))
return True

print("Found {ctestfiles} test files, creating virtualenv and running tests".format(ctestfiles=len(testfiles)))
print("Found {ctestfiles} test files, creating virtualenv and running tests".format(ctestfiles=len(get_testfiles(p))))
print("##[group]{p.name}".format(p=p))

# Create a virtual env
Expand Down Expand Up @@ -295,8 +307,65 @@ def run_one(p: Plugin) -> bool:
finally:
print("##[endgroup]")


def run_all(args):
def configure_git():
# Git requires some user and email to be configured in order to work in the context of GitHub Actions.
subprocess.run(
["git", "config", "--global", "user.email", '"lightningd@github.plugins.repo"']
)
subprocess.run(["git", "config", "--global", "user.name", '"lightningd"'])


# gather data
def collect_gather_data(results, success):
gather_data = {}
for t in results:
p = t[0]
if has_testfiles(p):
if success or t[1]:
gather_data[p.name] = "passed"
else:
gather_data[p.name] = "failed"
return gather_data


def push_gather_data(data, workflow, python_version):
print("Pushing gather data...")
configure_git()
subprocess.run(["git", "fetch"])
subprocess.run(["git", "checkout", "badges"])
filenames_to_add = []
for plugin_name, result in data.items():
filenames_to_add.append(git_add_gather_data(
plugin_name, result, workflow, python_version
))
output = subprocess.check_output(list(chain(["git", "add", "-v"], filenames_to_add))).decode("utf-8")
print(f"output from git add: {output}")
if output != "":
output = subprocess.check_output(
[
"git",
"commit",
"-m",
f"Update test result for Python{python_version} to ({workflow} workflow)",
]
).decode("utf-8")
print(f"output from git commit: {output}")
subprocess.run(["git", "push", "origin", "badges"])
print("Done.")


def git_add_gather_data(plugin_name, result, workflow, python_version):
_dir = f".badges/gather_data/{workflow}/{plugin_name}"
filename = os.path.join(_dir, f"python{python_version}.txt")
os.makedirs(_dir, exist_ok=True)
with open(filename, "w") as file:
print(f"Writing {filename}")
file.write(result)

return filename


def run_all(workflow, python_version, update_badges, plugin_names):
root_path = subprocess.check_output([
'git',
'rev-parse',
Expand All @@ -306,20 +375,35 @@ def run_all(args):
root = Path(root_path)

plugins = list(enumerate_plugins(root))
if args != []:
plugins = [p for p in plugins if p.name in args]
if plugin_names != []:
plugins = [p for p in plugins if p.name in plugin_names]
print("Testing the following plugins: {names}".format(names=[p.name for p in plugins]))
else:
print("Testing all plugins in {root}".format(root=root))

results = [(p, run_one(p)) for p in plugins]
success = all([t[1] for t in results])

if update_badges:
push_gather_data(collect_gather_data(results, success), workflow, python_version)

if not success:
print("The following tests failed:")
for t in filter(lambda t: not t[1], results):
print(" - {p.name} ({p.path})".format(p=t[0]))
sys.exit(1)
else:
print("All tests passed.")


if __name__ == "__main__":
run_all(sys.argv[1:])
import argparse

parser = argparse.ArgumentParser(description='Plugins test script')
parser.add_argument("workflow", type=str, help="Name of the GitHub workflow")
parser.add_argument("python_version", type=str, help="Python version")
parser.add_argument("--update-badges", action='store_true', help="Whether badges data should be updated")
parser.add_argument("plugins", nargs="*", default=[], help="List of plugins")
args = parser.parse_args()

run_all(args.workflow, args.python_version, args.update_badges, args.plugins)
168 changes: 168 additions & 0 deletions .ci/update_badges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import json
import os
import subprocess
from collections import namedtuple
from pathlib import Path, PosixPath
from typing import Generator, List

Plugin = namedtuple(
"Plugin",
[
"name",
"path",
"language",
"framework",
"details",
],
)

exclude = [
".ci",
".git",
".github",
"archived",
"lightning",
]


def configure_git():
# Git needs some user and email to be configured in order to work in the context of GitHub Actions.
subprocess.run(
["git", "config", "--global", "user.email", '"lightningd@github.plugins.repo"']
)
subprocess.run(["git", "config", "--global", "user.name", '"lightningd"'])


def get_testfiles(p: Plugin) -> List[PosixPath]:
return [
x
for x in p.path.iterdir()
if (x.is_dir() and x.name == "tests")
or (x.name.startswith("test_") and x.name.endswith(".py"))
]


def has_testfiles(p: Plugin) -> bool:
return len(get_testfiles(p)) > 0


def list_plugins(plugins):
return ", ".join([p.name for p in sorted(plugins)])


def enumerate_plugins(basedir: Path) -> Generator[Plugin, None, None]:
plugins = list(
[x for x in basedir.iterdir() if x.is_dir() and x.name not in exclude]
)
pip_pytest = [x for x in plugins if (x / Path("requirements.txt")).exists()]
print(f"Pip plugins: {list_plugins(pip_pytest)}")

poetry_pytest = [x for x in plugins if (x / Path("pyproject.toml")).exists()]
print(f"Poetry plugins: {list_plugins(poetry_pytest)}")

other_plugins = [
x for x in plugins if x not in pip_pytest and x not in poetry_pytest
]
print(f"Other plugins: {list_plugins(other_plugins)}")

for p in sorted(pip_pytest):
yield Plugin(
name=p.name,
path=p,
language="python",
framework="pip",
details={
"requirements": p / Path("requirements.txt"),
"devrequirements": p / Path("requirements-dev.txt"),
},
)

for p in sorted(poetry_pytest):
yield Plugin(
name=p.name,
path=p,
language="python",
framework="poetry",
details={
"pyproject": p / Path("pyproject.toml"),
},
)

for p in sorted(other_plugins):
yield Plugin(
name=p.name,
path=p,
language="other",
framework="generic",
details={
"requirements": p / Path("tests/requirements.txt"),
"setup": p / Path("tests/setup.sh"),
},
)


def update_and_commit_badge(plugin_name, passed, workflow):
json_data = { "schemaVersion": 1, "label": "", "message": " ✔ ", "color": "green" }
if not passed:
json_data.update({"message": "✗", "color": "red"})

filename = os.path.join(".badges", f"{plugin_name}_{workflow}.json")
with open(filename, "w") as file:
file.write(json.dumps(json_data))

output = subprocess.check_output(["git", "add", "-v", filename]).decode("utf-8")
if output != "":
subprocess.run(["git", "commit", "-m", f'Update {plugin_name} badge to {"passed" if passed else "failed"} ({workflow})'])
return True
return False


def push_badges_data(workflow, num_of_python_versions):
print("Pushing badges data...")
configure_git()
subprocess.run(["git", "fetch"])
subprocess.run(["git", "checkout", "badges"])
subprocess.run(["git", "pull"])

root_path = (
subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
.decode("ASCII")
.strip()
)
plugins = list(enumerate_plugins(Path(root_path)))

any_changes = False
for plugin in plugins:
results = []
_dir = f".badges/gather_data/main/{plugin.name}"
if os.path.exists(_dir):
for child in Path(_dir).iterdir():
result = child.read_text().strip()
results.append(result)
print(f"Results for {child}: {result}")

passed = False
if (
len(set(results)) == 1
and results[0] == "passed"
# and len(results) == num_of_python_versions # TODO: Disabled as gather data for python versions is missing sporadingly.
):
passed = True
any_changes |= update_and_commit_badge(plugin.name, passed, workflow)

if any_changes:
subprocess.run(["git", "push", "origin", "badges"])
print("Done.")


if __name__ == "__main__":
import argparse

parser = argparse.ArgumentParser(description="Plugins completion script")
parser.add_argument("workflow", type=str, help="Name of the GitHub workflow")
parser.add_argument(
"num_of_python_versions", type=str, help="Number of Python versions"
)
args = parser.parse_args()

push_badges_data(args.workflow, int(args.num_of_python_versions))
Loading
Loading