11import argparse
22import asyncio
3+ import glob
34import os
45import sys
56import time
1516from ci_tools .scenario .generation import build_whl_for_req , replace_dev_reqs
1617from ci_tools .logging import configure_logging , logger
1718from ci_tools .environment_exclusions import is_check_enabled , CHECK_DEFAULTS
19+ from ci_tools .parsing import get_config_setting
1820from devtools_testutils .proxy_startup import prepare_local_tool
1921from packaging .requirements import Requirement
2022
@@ -49,6 +51,7 @@ class ProxyProcess:
4951 "sdist" ,
5052 "devtest" ,
5153 "optional" ,
54+ "import_all" ,
5255 "latestdependency" ,
5356 "mindependency" ,
5457}
@@ -84,9 +87,7 @@ def _compare_req_to_injected_reqs(parsed_req, injected_packages: List[str]) -> b
8487 return any (parsed_req .name in req for req in injected_packages )
8588
8689
87- def _inject_custom_reqs (
88- req_file : str , injected_packages : str , package_dir : str
89- ) -> None :
90+ def _inject_custom_reqs (req_file : str , injected_packages : str , package_dir : str ) -> None :
9091 req_lines = []
9192 injected_list = [p for p in re .split (r"[\s,]" , injected_packages ) if p ]
9293
@@ -115,8 +116,7 @@ def _inject_custom_reqs(
115116 all_adjustments = installable + [
116117 line_tuple [0 ].strip ()
117118 for line_tuple in req_lines
118- if line_tuple [0 ].strip ()
119- and not _compare_req_to_injected_reqs (line_tuple [1 ], all_filter_names )
119+ if line_tuple [0 ].strip () and not _compare_req_to_injected_reqs (line_tuple [1 ], all_filter_names )
120120 ]
121121 else :
122122 all_adjustments = installable
@@ -138,6 +138,7 @@ async def run_check(
138138 mark_arg : Optional [str ],
139139 dest_dir : Optional [str ] = None ,
140140 service : Optional [str ] = None ,
141+ python_version : Optional [str ] = None ,
141142) -> CheckResult :
142143 """Run a single check (subprocess) within a concurrency semaphore, capturing output and timing.
143144
@@ -161,6 +162,8 @@ async def run_check(
161162 async with semaphore :
162163 start = time .time ()
163164 cmd = base_args + [check , "--isolate" , package ]
165+ if python_version :
166+ cmd += ["--python" , python_version ]
164167 if service :
165168 cmd += ["--service" , service ]
166169 if mark_arg :
@@ -172,9 +175,7 @@ async def run_check(
172175 env ["PROXY_URL" ] = f"http://localhost:{ proxy_port } "
173176
174177 if in_ci ():
175- env ["PROXY_ASSETS_FOLDER" ] = os .path .join (
176- root_dir , ".assets_distributed" , str (proxy_port )
177- )
178+ env ["PROXY_ASSETS_FOLDER" ] = os .path .join (root_dir , ".assets_distributed" , str (proxy_port ))
178179 try :
179180 logger .info (" " .join (cmd ))
180181 proc = await asyncio .create_subprocess_exec (
@@ -194,9 +195,7 @@ async def run_check(
194195 stderr = stderr_b .decode (errors = "replace" )
195196 exit_code = proc .returncode or 0
196197 status = "OK" if exit_code == 0 else f"FAIL({ exit_code } )"
197- logger .info (
198- f"[END { idx } /{ total } ] { check } :: { package } -> { status } in { duration :.2f} s"
199- )
198+ logger .info (f"[END { idx } /{ total } ] { check } :: { package } -> { status } in { duration :.2f} s" )
200199 # Print captured output after completion to avoid interleaving
201200 header = f"===== OUTPUT: { check } :: { package } (exit { exit_code } ) ====="
202201 trailer = "=" * len (header )
@@ -220,10 +219,10 @@ async def run_check(
220219 # finally, we need to clean up any temp dirs created by --isolate
221220 if in_ci ():
222221 package_name = os .path .basename (os .path .normpath (package ))
223- isolate_dir = os .path .join (
224- root_dir , ".venv" , package_name , f" .venv_{ check } "
225- )
226- ISOLATE_DIRS_TO_CLEAN .append (isolate_dir )
222+ venv_pkg_root = os .path .join (root_dir , ".venv" , package_name )
223+ # match both .venv_{check} and version-qualified .venv_{check}_py311 etc.
224+ for d in glob . glob ( os . path . join ( venv_pkg_root , f".venv_ { check } *" )):
225+ ISOLATE_DIRS_TO_CLEAN .append (d )
227226 return CheckResult (package , check , exit_code , duration , stdout , stderr )
228227
229228
@@ -247,14 +246,10 @@ def summarize(results: List[CheckResult]) -> int:
247246 print ("-" * len (header ))
248247 for r in sorted (results , key = lambda x : (x .exit_code != 0 , x .package , x .check )):
249248 status = "OK" if r .exit_code == 0 else f"FAIL({ r .exit_code } )"
250- print (
251- f"{ r .package .ljust (pkg_w )} { r .check .ljust (chk_w )} { status .ljust (8 )} { r .duration :>10.2f} "
252- )
249+ print (f"{ r .package .ljust (pkg_w )} { r .check .ljust (chk_w )} { status .ljust (8 )} { r .duration :>10.2f} " )
253250 worst = max ((r .exit_code for r in results ), default = 0 )
254251 failed = [r for r in results if r .exit_code != 0 ]
255- print (
256- f"\n Total checks: { len (results )} | Failed: { len (failed )} | Worst exit code: { worst } "
257- )
252+ print (f"\n Total checks: { len (results )} | Failed: { len (failed )} | Worst exit code: { worst } " )
258253 return worst
259254
260255
@@ -292,14 +287,10 @@ async def run_all_checks(
292287 dependency_tools_path = os .path .join (root_dir , "eng" , "dependency_tools.txt" )
293288
294289 if in_ci ():
295- logger .info (
296- "Replacing relative requirements in eng/test_tools.txt with prebuilt wheels."
297- )
290+ logger .info ("Replacing relative requirements in eng/test_tools.txt with prebuilt wheels." )
298291 replace_dev_reqs (test_tools_path , root_dir , wheel_dir )
299292
300- logger .info (
301- "Replacing relative requirements in eng/dependency_tools.txt with prebuilt wheels."
302- )
293+ logger .info ("Replacing relative requirements in eng/dependency_tools.txt with prebuilt wheels." )
303294 replace_dev_reqs (dependency_tools_path , root_dir , wheel_dir )
304295
305296 for pkg in packages :
@@ -321,15 +312,19 @@ async def run_all_checks(
321312 if not is_check_enabled (package , check , CHECK_DEFAULTS .get (check , True )):
322313 logger .warning (f"Skipping disabled check { check } for package { package } " )
323314 continue
324- logger .info (
325- f"Assigning proxy port { next_proxy_port } to check { check } for package { package } "
326- )
327- scheduled .append ((package , check , next_proxy_port ))
315+ logger .info (f"Assigning proxy port { next_proxy_port } to check { check } for package { package } " )
316+
317+ # Check if this package overrides the Python version for analysis
318+ pkg_python_version = get_config_setting (package , "analyze_python_version" , None )
319+ if pkg_python_version :
320+ logger .info (f"Package { package } overrides analyze Python version to { pkg_python_version } " )
321+
322+ scheduled .append ((package , check , next_proxy_port , pkg_python_version ))
328323 next_proxy_port += 1
329324
330325 total = len (scheduled )
331326
332- for idx , (package , check , proxy_port ) in enumerate (scheduled , start = 1 ):
327+ for idx , (package , check , proxy_port , pkg_python_version ) in enumerate (scheduled , start = 1 ):
333328 tasks .append (
334329 asyncio .create_task (
335330 run_check (
@@ -343,6 +338,7 @@ async def run_all_checks(
343338 mark_arg ,
344339 dest_dir ,
345340 service ,
341+ pkg_python_version ,
346342 )
347343 )
348344 )
@@ -358,15 +354,13 @@ async def run_all_checks(
358354 raise
359355 # Normalize exceptions
360356 norm_results : List [CheckResult ] = []
361- for res , (package , check , _ ) in zip (results , scheduled ):
357+ for res , (package , check , _ , _ ) in zip (results , scheduled ):
362358 if isinstance (res , CheckResult ):
363359 norm_results .append (res )
364360 elif isinstance (res , Exception ):
365361 norm_results .append (CheckResult (package , check , 99 , 0.0 , "" , str (res )))
366362 else :
367- norm_results .append (
368- CheckResult (package , check , 98 , 0.0 , "" , f"Unknown result type: { res } " )
369- )
363+ norm_results .append (CheckResult (package , check , 98 , 0.0 , "" , f"Unknown result type: { res } " ))
370364 return summarize (norm_results )
371365
372366
@@ -442,15 +436,11 @@ def handler(signum, frame):
442436 ),
443437 )
444438
445- parser .add_argument (
446- "--disablecov" , help = ("Flag. Disables code coverage." ), action = "store_true"
447- )
439+ parser .add_argument ("--disablecov" , help = ("Flag. Disables code coverage." ), action = "store_true" )
448440
449441 parser .add_argument (
450442 "--service" ,
451- help = (
452- "Name of service directory (under sdk/) to test. Example: --service applicationinsights"
453- ),
443+ help = ("Name of service directory (under sdk/) to test. Example: --service applicationinsights" ),
454444 )
455445
456446 parser .add_argument (
@@ -517,9 +507,7 @@ def handler(signum, frame):
517507 else :
518508 target_dir = root_dir
519509
520- logger .info (
521- f"Beginning discovery for { args .service } and root dir { root_dir } . Resolving to { target_dir } ."
522- )
510+ logger .info (f"Beginning discovery for { args .service } and root dir { root_dir } . Resolving to { target_dir } ." )
523511
524512 # ensure that recursive virtual envs aren't messed with by this call
525513 os .environ .pop ("VIRTUAL_ENV" , None )
@@ -536,9 +524,7 @@ def handler(signum, frame):
536524 )
537525
538526 if len (targeted_packages ) == 0 :
539- logger .info (
540- f"No packages collected for targeting string { args .glob_string } and root dir { root_dir } . Exit 0."
541- )
527+ logger .info (f"No packages collected for targeting string { args .glob_string } and root dir { root_dir } . Exit 0." )
542528 exit (0 )
543529
544530 logger .info (f"Executing checks with the executable { sys .executable } ." )
@@ -570,9 +556,7 @@ def handler(signum, frame):
570556 try :
571557 proxy_executable = prepare_local_tool (root_dir )
572558 except Exception as exc :
573- logger .error (
574- f"Unable to prepare test proxy executable for recording restore: { exc } "
575- )
559+ logger .error (f"Unable to prepare test proxy executable for recording restore: { exc } " )
576560 sys .exit (1 )
577561
578562 logger .info (
@@ -583,9 +567,7 @@ def handler(signum, frame):
583567 proxy_processes : List [ProxyProcess ] = []
584568 try :
585569 if in_ci ():
586- logger .info (
587- f"Ensuring { len (checks )} test proxies are running for requested checks..."
588- )
570+ logger .info (f"Ensuring { len (checks )} test proxies are running for requested checks..." )
589571 # Pass through service if set and not "auto"
590572 effective_service = args .service if (args .service and args .service != "auto" ) else None
591573 exit_code = asyncio .run (
0 commit comments