Skip to content

Commit f209b31

Browse files
committed
CI: support test frameworks in tests dir
1 parent 3f92472 commit f209b31

File tree

2 files changed

+179
-40
lines changed

2 files changed

+179
-40
lines changed

.ci/test.py

Lines changed: 99 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pathlib import Path
1212
from typing import Tuple
1313

14-
from utils import Plugin, configure_git, enumerate_plugins
14+
from utils import Plugin, configure_git, enumerate_plugins, get_framework_working_dir
1515

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

@@ -32,18 +32,59 @@ def prepare_env(p: Plugin, workflow: str) -> Tuple[dict, tempfile.TemporaryDirec
3232
directory = p.path / ".venv"
3333

3434
if p.framework == "uv":
35+
cwd = p.details["pyproject"].parent.resolve()
3536
if workflow == "nightly":
3637
cln_path = os.environ["CLN_PATH"]
3738
try:
38-
subprocess.check_call(["uv", "add", "--editable", cln_path + "/contrib/pyln-testing", cln_path + "/contrib/pyln-client", cln_path + "/contrib/pyln-proto"], cwd=p.path.resolve())
39+
subprocess.check_call(
40+
[
41+
"uv",
42+
"add",
43+
"--editable",
44+
cln_path + "/contrib/pyln-testing",
45+
cln_path + "/contrib/pyln-client",
46+
cln_path + "/contrib/pyln-proto",
47+
],
48+
cwd=cwd,
49+
)
3950
except: # noqa: E722
40-
subprocess.check_call(["uv", "add", "--dev", "--editable", cln_path + "/contrib/pyln-testing", cln_path + "/contrib/pyln-client", cln_path + "/contrib/pyln-proto"], cwd=p.path.resolve())
51+
subprocess.check_call(
52+
[
53+
"uv",
54+
"add",
55+
"--dev",
56+
"--editable",
57+
cln_path + "/contrib/pyln-testing",
58+
cln_path + "/contrib/pyln-client",
59+
cln_path + "/contrib/pyln-proto",
60+
],
61+
cwd=cwd,
62+
)
4163
else:
42-
pyln_version = re.sub(r'\.0(\d+)', r'.\1', workflow)
64+
pyln_version = re.sub(r"\.0(\d+)", r".\1", workflow)
4365
try:
44-
subprocess.check_call(["uv", "add", f"pyln-testing=={pyln_version}", f"pyln-client=={pyln_version}", f"pyln-proto=={pyln_version}"], cwd=p.path.resolve())
66+
subprocess.check_call(
67+
[
68+
"uv",
69+
"add",
70+
f"pyln-testing=={pyln_version}",
71+
f"pyln-client=={pyln_version}",
72+
f"pyln-proto=={pyln_version}",
73+
],
74+
cwd=cwd,
75+
)
4576
except: # noqa: E722
46-
subprocess.check_call(["uv", "add", "--dev", f"pyln-testing=={pyln_version}", f"pyln-client=={pyln_version}", f"pyln-proto=={pyln_version}"], cwd=p.path.resolve())
77+
subprocess.check_call(
78+
[
79+
"uv",
80+
"add",
81+
"--dev",
82+
f"pyln-testing=={pyln_version}",
83+
f"pyln-client=={pyln_version}",
84+
f"pyln-proto=={pyln_version}",
85+
],
86+
cwd=cwd,
87+
)
4788
else:
4889
# Create a temporary directory for virtualenv
4990
vdir = tempfile.TemporaryDirectory()
@@ -109,7 +150,7 @@ def prepare_env_poetry(p: Plugin, directory: Path, workflow: str) -> bool:
109150
)
110151

111152
# We run all commands in the plugin directory so poetry remembers its settings
112-
workdir = p.path.resolve()
153+
workdir = p.details["pyproject"].parent.resolve()
113154

114155
logging.info(f"Using poetry at {poetry} ({python3}) to run tests in {workdir}")
115156

@@ -199,7 +240,7 @@ def prepare_generic(p: Plugin, directory: Path, env: dict, workflow: str) -> boo
199240
pip_path = directory / "bin" / "pip3"
200241

201242
# Now install all the requirements
202-
if p.details["requirements"].exists():
243+
if "requirements" in p.details:
203244
print(f"Installing requirements from {p.details['requirements']}")
204245
subprocess.check_call(
205246
[pip_path, "install", *pip_opts, "-r", p.details["requirements"]],
@@ -231,7 +272,7 @@ def install_pyln_testing(pip_path, workflow: str):
231272
stderr=subprocess.STDOUT,
232273
)
233274

234-
pyln_version = re.sub(r'\.0(\d+)', r'.\1', workflow)
275+
pyln_version = re.sub(r"\.0(\d+)", r".\1", workflow)
235276

236277
subprocess.check_call(
237278
[
@@ -309,14 +350,15 @@ def run_one(p: Plugin, workflow: str, timings: dict) -> bool:
309350
raise RuntimeError(f"pytest not found in PATH:{env['PATH']}")
310351
cmd = [pytest_path] + cmd
311352

312-
logging.info(f"Running `{' '.join(cmd)}` in directory {p.path.resolve()}")
353+
cwd = get_framework_working_dir(p)
354+
logging.info(f"Running `{' '.join(cmd)}` in directory {cwd}")
313355
start_tests = time.perf_counter()
314356
try:
315357
subprocess.check_call(
316358
cmd,
317359
stderr=subprocess.STDOUT,
318360
env=env,
319-
cwd=p.path.resolve(),
361+
cwd=cwd,
320362
)
321363
timings[p.name]["tests"] = time.perf_counter() - start_tests
322364
return True
@@ -337,10 +379,31 @@ def run_one_reckless(p: Plugin, workflow: str, timings: dict) -> bool:
337379

338380
if workflow == "nightly":
339381
cln_path = os.environ["CLN_PATH"]
340-
subprocess.check_call(["uv", "add", "--dev", "--editable", cln_path + "/contrib/pyln-testing", cln_path + "/contrib/pyln-client", cln_path + "/contrib/pyln-proto"], cwd=reckles_testing)
382+
subprocess.check_call(
383+
[
384+
"uv",
385+
"add",
386+
"--dev",
387+
"--editable",
388+
cln_path + "/contrib/pyln-testing",
389+
cln_path + "/contrib/pyln-client",
390+
cln_path + "/contrib/pyln-proto",
391+
],
392+
cwd=reckles_testing,
393+
)
341394
else:
342-
pyln_version = re.sub(r'\.0(\d+)', r'.\1', workflow)
343-
subprocess.check_call(["uv", "add", "--dev", f"pyln-testing=={pyln_version}", f"pyln-client=={pyln_version}", f"pyln-proto=={pyln_version}"], cwd=reckles_testing)
395+
pyln_version = re.sub(r"\.0(\d+)", r".\1", workflow)
396+
subprocess.check_call(
397+
[
398+
"uv",
399+
"add",
400+
"--dev",
401+
f"pyln-testing=={pyln_version}",
402+
f"pyln-client=={pyln_version}",
403+
f"pyln-proto=={pyln_version}",
404+
],
405+
cwd=reckles_testing,
406+
)
344407

345408
cmd = [
346409
"uv",
@@ -352,7 +415,7 @@ def run_one_reckless(p: Plugin, workflow: str, timings: dict) -> bool:
352415
"--color=yes",
353416
"test_reckless.py",
354417
"--plugin",
355-
p.name
418+
p.name,
356419
]
357420

358421
start_tests = time.perf_counter()
@@ -373,11 +436,13 @@ def run_one_reckless(p: Plugin, workflow: str, timings: dict) -> bool:
373436

374437

375438
# gather data
376-
def collect_gather_data(results: list, success: bool, need_testfiles: bool = True) -> dict:
439+
def collect_gather_data(
440+
results: list, success: bool, need_testfiles: bool = True
441+
) -> dict:
377442
gather_data = {}
378443
for t in results:
379444
p = t[0]
380-
if p.testfiles or not need_testfiles:
445+
if p.testfiles or not need_testfiles:
381446
if success or t[1]:
382447
gather_data[p.name] = "passed"
383448
else:
@@ -392,7 +457,9 @@ def push_gather_data(data: dict, workflow: str, python_version: str, suffix: str
392457
subprocess.run(["git", "checkout", "badges"])
393458
filenames_to_add = []
394459
for plugin_name, result in data.items():
395-
filename = write_gather_data_file(plugin_name, result, workflow, python_version, suffix)
460+
filename = write_gather_data_file(
461+
plugin_name, result, workflow, python_version, suffix
462+
)
396463
filenames_to_add.append(filename)
397464
output = subprocess.check_output(
398465
list(chain(["git", "add", "-v"], filenames_to_add))
@@ -404,7 +471,9 @@ def push_gather_data(data: dict, workflow: str, python_version: str, suffix: str
404471
"git",
405472
"commit",
406473
"-m",
407-
"Update" + (f" {suffix}" if suffix else " ") + f"test result for Python{python_version} to ({workflow} workflow)",
474+
"Update"
475+
+ (f" {suffix}" if suffix else " ")
476+
+ f"test result for Python{python_version} to ({workflow} workflow)",
408477
]
409478
).decode("utf-8")
410479
print(f"output from git commit: {output}")
@@ -428,7 +497,11 @@ def push_gather_data(data: dict, workflow: str, python_version: str, suffix: str
428497
def write_gather_data_file(
429498
plugin_name: str, result, workflow: str, python_version: str, suffix: str = ""
430499
) -> str:
431-
_dir = ".badges" + (f"_{suffix}" if suffix else "") + f"/gather_data/{workflow}/{plugin_name}"
500+
_dir = (
501+
".badges"
502+
+ (f"_{suffix}" if suffix else "")
503+
+ f"/gather_data/{workflow}/{plugin_name}"
504+
)
432505
filename = os.path.join(_dir, f"python{python_version}.txt")
433506
os.makedirs(_dir, exist_ok=True)
434507
with open(filename, "w") as file:
@@ -441,7 +514,7 @@ def write_gather_data_file(
441514
def gather_old_failures(old_failures: list, workflow: str, suffix: str = ""):
442515
print("Gather old" + (f" {suffix}" if suffix else " ") + "failures...")
443516
configure_git()
444-
517+
445518
subprocess.run(["git", "fetch", "origin", "badges"], check=True)
446519
subprocess.run(["git", "checkout", "badges"], check=True)
447520

@@ -503,7 +576,7 @@ def run_all(
503576
env_str = f"{env:9.2f}s" if env is not None else " (not run)"
504577
tests_str = f"{tests:9.2f}s" if tests is not None else " (not run)"
505578
reckless_str = f"{reckless:9.2f}s" if reckless is not None else " (not run)"
506-
579+
507580
print(f"{plugin:<35} env:{env_str} tests:{tests_str} reckless:{reckless_str}")
508581

509582
old_failures = []
@@ -519,7 +592,10 @@ def run_all(
519592
collect_gather_data(results, success), workflow, python_version
520593
)
521594
push_gather_data(
522-
collect_gather_data(results_reckless, success_reckless, False), workflow, python_version, "reckless"
595+
collect_gather_data(results_reckless, success_reckless, False),
596+
workflow,
597+
python_version,
598+
"reckless",
523599
)
524600

525601
if not success or not success_reckless:

.ci/utils.py

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,71 +54,134 @@ def list_plugins(plugins: list) -> str:
5454
return ", ".join([p.name for p in sorted(plugins)])
5555

5656

57+
def get_test_framework(p: Path, filename: str):
58+
if p is None:
59+
return None
60+
for candidate in [p / filename, p / "tests" / filename]:
61+
if candidate.exists():
62+
return candidate
63+
return None
64+
65+
66+
def get_framework_working_dir(p: Plugin) -> Path:
67+
if p.framework == "uv":
68+
return p.details["pyproject"].parent.resolve()
69+
elif p.framework == "poetry":
70+
return p.details["pyproject"].parent.resolve()
71+
elif p.framework == "pip":
72+
if "requirements" in p.details:
73+
return p.details["requirements"].parent.resolve()
74+
elif "devrequirements" in p.details:
75+
return p.details["devrequirements"].parent.resolve()
76+
elif p.framework == "generic":
77+
if "requirements" in p.details:
78+
return p.details["requirements"].parent.resolve()
79+
return p.path.resolve()
80+
return p.path.resolve()
81+
82+
5783
def enumerate_plugins(basedir: Path) -> Generator[Plugin, None, None]:
5884
plugins = list(
5985
[x for x in basedir.iterdir() if x.is_dir() and x.name not in exclude]
6086
)
6187

62-
pip_pytest = [x for x in plugins if (x / Path("requirements.txt")).exists()]
88+
pip_pytest = [
89+
x for x in plugins if get_test_framework(x, "requirements.txt") is not None
90+
]
6391
print(f"Pip plugins: {list_plugins(pip_pytest)}")
6492

65-
uv_pytest = [x for x in plugins if (x / Path("uv.lock")).exists()]
93+
uv_pytest = [x for x in plugins if get_test_framework(x, "uv.lock") is not None]
6694
print(f"Uv plugins: {list_plugins(uv_pytest)}")
6795

6896
# Don't double detect plugins migrating to uv
69-
poetry_pytest = [x for x in plugins if (x / Path("poetry.lock")).exists() and x not in uv_pytest]
97+
poetry_pytest = [
98+
x
99+
for x in plugins
100+
if (get_test_framework(x, "poetry.lock") is not None) and x not in uv_pytest
101+
]
70102
print(f"Poetry plugins: {list_plugins(poetry_pytest)}")
71103

72104
generic_plugins = [
73-
x for x in plugins if x not in pip_pytest and x not in poetry_pytest and x not in uv_pytest
105+
x
106+
for x in plugins
107+
if x not in pip_pytest and x not in poetry_pytest and x not in uv_pytest
74108
]
75109
print(f"Generic plugins: {list_plugins(generic_plugins)}")
76110

77111
for p in sorted(pip_pytest):
112+
details = {}
113+
114+
req = get_test_framework(p, "requirements.txt")
115+
if req:
116+
details["requirements"] = req
117+
118+
devreq = get_test_framework(p, "requirements-dev.txt")
119+
if devreq:
120+
details["devrequirements"] = devreq
121+
122+
if details == {}:
123+
print(f"Could not find requirements in {p}")
124+
continue
125+
78126
yield Plugin(
79127
name=p.name,
80128
path=p,
81129
language="python",
82130
framework="pip",
83131
testfiles=get_testfiles(p),
84-
details={
85-
"requirements": p / Path("requirements.txt"),
86-
"devrequirements": p / Path("requirements-dev.txt"),
87-
},
132+
details=details,
88133
)
89134

90135
for p in sorted(poetry_pytest):
136+
details = {}
137+
138+
req = get_test_framework(p, "pyproject.toml")
139+
if req:
140+
details["pyproject"] = req
141+
else:
142+
print(f"Could not find pyproject.toml in {p}")
143+
continue
144+
91145
yield Plugin(
92146
name=p.name,
93147
path=p,
94148
language="python",
95149
framework="poetry",
96150
testfiles=get_testfiles(p),
97-
details={
98-
"pyproject": p / Path("pyproject.toml"),
99-
},
151+
details=details,
100152
)
101153

102154
for p in sorted(uv_pytest):
155+
details = {}
156+
157+
req = get_test_framework(p, "pyproject.toml")
158+
if req:
159+
details["pyproject"] = req
160+
else:
161+
print(f"Could not find pyproject.toml in {p}")
162+
continue
163+
103164
yield Plugin(
104165
name=p.name,
105166
path=p,
106167
language="python",
107168
framework="uv",
108169
testfiles=get_testfiles(p),
109-
details={
110-
"pyproject": p / Path("pyproject.toml"),
111-
},
170+
details=details,
112171
)
113172

114173
for p in sorted(generic_plugins):
174+
details = {}
175+
176+
req = get_test_framework(p, "requirements.txt")
177+
if req:
178+
details["requirements"] = req
179+
115180
yield Plugin(
116181
name=p.name,
117182
path=p,
118183
language="other",
119184
framework="generic",
120185
testfiles=get_testfiles(p),
121-
details={
122-
"requirements": p / Path("tests/requirements.txt"),
123-
},
186+
details=details,
124187
)

0 commit comments

Comments
 (0)