Skip to content

Commit 2dec301

Browse files
authored
[confcom] Run testing against built extension (#9429)
* [confcom] Run testing against built extension * Fix style * Remove flag only supported on later python versions * . * Work around azdev test not respecting the session scope * Add robust session scope mechanism for azdev test
1 parent c34d777 commit 2dec301

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
import fcntl
7+
import importlib
8+
import os
9+
import subprocess
10+
import tempfile
11+
import psutil
12+
import pytest
13+
import sys
14+
import shutil
15+
16+
from pathlib import Path
17+
18+
19+
# This fixture ensures tests are run against final built wheels of the extension
20+
# instead of the unbuilt local code, which may have breaking differences with
21+
# the thing we actually ship to users. All but the test modules themselves are
22+
# replaced with the wheel in case the tests themselves rely on unshipped code.
23+
@pytest.fixture(autouse=True, scope="session")
24+
def run_on_wheel(request):
25+
26+
modules_to_test = {i.module for i in request.session.items}
27+
extensions_to_build = {module.__name__.split(".")[0] for module in modules_to_test}
28+
extension_dirs = {Path(a.split("/azext_")[0]) for a in request.config.args}
29+
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__)
76+
77+
yield

0 commit comments

Comments
 (0)