33# Licensed under the MIT License. See License.txt in the project root for license information.
44# --------------------------------------------------------------------------------------------
55
6- import functools
6+ import fcntl
77import importlib
8+ import os
89import subprocess
910import tempfile
11+ import psutil
1012import pytest
1113import sys
1214import shutil
1315
1416from 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