Skip to content

Commit 1f57a4a

Browse files
committed
Add robust session scope mechanism for azdev test
1 parent d5ea516 commit 1f57a4a

File tree

1 file changed

+49
-33
lines changed

1 file changed

+49
-33
lines changed

src/confcom/azext_confcom/tests/conftest.py

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,19 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55

6-
import functools
6+
import fcntl
77
import importlib
8+
import os
89
import subprocess
910
import tempfile
11+
import psutil
1012
import pytest
1113
import sys
1214
import shutil
1315

1416
from pathlib import Path
1517

1618

17-
# This is required to work around the implementation of azdev test. It runs
18-
# separate pytest invocations for each test, making it impossible to respect the
19-
# session scope. Therefore, we need to manually ensure wheels are not built
20-
# separately for each test.
21-
@functools.lru_cache()
22-
def get_wheel_dir():
23-
return Path(tempfile.mkdtemp(prefix="confcom_wheels_"))
24-
25-
2619
# This fixture ensures tests are run against final built wheels of the extension
2720
# instead of the unbuilt local code, which may have breaking differences with
2821
# the thing we actually ship to users. All but the test modules themselves are
@@ -34,28 +27,51 @@ def run_on_wheel(request):
3427
extensions_to_build = {module.__name__.split(".")[0] for module in modules_to_test}
3528
extension_dirs = {Path(a.split("/azext_")[0]) for a in request.config.args}
3629

37-
build_dir = get_wheel_dir()
38-
if not any(build_dir.iterdir()):
39-
40-
# Delete the extensions build dir, as azdev extension build doesn't
41-
# reliably handle changes
42-
for extension_dir in extension_dirs:
43-
if (extension_dir / "build").exists():
44-
shutil.rmtree((extension_dir / "build").as_posix(), ignore_errors=True)
45-
46-
# Build all extensions being tested into wheels
47-
for extension in extensions_to_build:
48-
subprocess.run(
49-
["azdev", "extension", "build", extension.replace("azext_", ""), "--dist-dir", build_dir.as_posix()],
50-
check=True,
51-
)
52-
53-
# Add the wheel to the path and reload extension modules so the
54-
# tests pick up the wheel code over the unbuilt code
55-
sys.path.insert(0, build_dir.glob("*.whl").__next__().as_posix())
56-
for module in list(sys.modules.values()):
57-
if extension in module.__name__ and module not in modules_to_test:
58-
del sys.modules[module.__name__]
59-
importlib.import_module(module.__name__)
30+
# Azdev doesn't respect the session scope of the fixture, therefore we need
31+
# to implement equivalent behaviour by getting a unique ID for the current
32+
# run and using that to determine if wheels have already been built. Search
33+
# process parentage until we find the first shell process and use it's
34+
# child's PID as the run ID.
35+
parent = psutil.Process(os.getpid())
36+
while not any(parent.cmdline()[0].endswith(i) for i in ["bash", "sh"]):
37+
parent = parent.parent()
38+
RUN_ID = parent.children()[0].pid
39+
40+
build_dir = Path(tempfile.gettempdir()) / f"wheels_{RUN_ID}"
41+
build_dir.mkdir(exist_ok=True)
42+
43+
# Build all extensions being tested into wheels
44+
for extension in extensions_to_build:
45+
46+
extension_name = extension.replace("azext_", "")
47+
48+
# Ensure we acquire a lock while operating on the build dir to avoid races
49+
lock_file = build_dir / ".dir.lock"
50+
with lock_file.open("w") as f:
51+
fcntl.flock(f, fcntl.LOCK_EX)
52+
try:
53+
54+
# Delete the extensions build dir, as azdev extension build doesn't
55+
# reliably handle changes
56+
for extension_dir in extension_dirs:
57+
if (extension_dir / "build").exists():
58+
shutil.rmtree((extension_dir / "build").as_posix(), ignore_errors=True)
59+
60+
if not any(build_dir.glob(f"{extension_name}*.whl")):
61+
subprocess.run(
62+
["azdev", "extension", "build", extension.replace("azext_", ""), "--dist-dir", build_dir.as_posix()],
63+
check=True,
64+
)
65+
66+
finally:
67+
fcntl.flock(f, fcntl.LOCK_UN)
68+
69+
# Add the wheel to the path and reload extension modules so the
70+
# tests pick up the wheel code over the unbuilt code
71+
sys.path.insert(0, build_dir.glob("*.whl").__next__().as_posix())
72+
for module in list(sys.modules.values()):
73+
if extension in module.__name__ and module not in modules_to_test:
74+
del sys.modules[module.__name__]
75+
importlib.import_module(module.__name__)
6076

6177
yield

0 commit comments

Comments
 (0)