Skip to content

Commit c568398

Browse files
CopilotNateD-MSFT
andauthored
Fix ApiValidator failures, harden Dvl.exe check, parallelize test runner
- Add src/drivers/test/Directory.Build.targets that overrides the WDK's ApiValidator MSBuild target with an empty target. The WDK NuGet packages reference ApiValidator.exe in WindowsDriver.common.targets but do not ship the binary itself, so the post-build step always failed with MSB3721 for builds using the Universal driver target platform. Replacing the target with a no-op cleanly suppresses the step; API validation is irrelevant for the CodeQL static analysis these tests perform. This is the cause of the 6 still-failing tests (UnsafeCallInGlobalInit, MultithreadedAVCondition, StaticInitializer, DeviceInitApi, FloatSafeExit, FloatUnsafeExit). - Remove the previous <ApiValidatorEnabled>false</ApiValidatorEnabled> workaround from Directory.Build.props -- the WDK targets do not honor that property name, so it had no effect. - dvl_tests.ps1: when Dvl.exe cannot be located in either the NuGet packages directory or the system WDK install path, exit with a clear failure instead of silently skipping the dvl command-type tests. Skipping let regressions go undetected. - build_create_analyze_test.py: parallelize the test runner. Each ql_test uses isolated working/<name>, TestDB/<name>, and AnalysisFiles/<name>.sarif paths, so multiple tests are safe to execute concurrently. Use multiprocessing.pool.ThreadPool (already imported) with a worker count controlled by a new -j/--jobs flag, defaulting to os.cpu_count(). Pass --jobs 1 to fall back to the legacy sequential behaviour. Print output is already protected by print_mutex; added results_mutex around the shared health_df / detailed_health_df DataFrame writes. Refactored the body of the per-test loop into _run_single_test for use by the pool's imap_unordered. Agent-Logs-Url: https://github.com/microsoft/Windows-Driver-Developer-Supplemental-Tools/sessions/71e6036e-2c98-4bb7-85ab-2270cbce9c68 Co-authored-by: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com>
1 parent 8b61a8d commit c568398

3 files changed

Lines changed: 84 additions & 20 deletions

File tree

src/drivers/test/Directory.Build.props

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,6 @@
1414
-->
1515
<PropertyGroup>
1616
<WdkNuGetPackagesDir>$(MSBuildThisFileDirectory)..\..\..\packages\</WdkNuGetPackagesDir>
17-
<!--
18-
ApiValidator.exe is not shipped in the WDK NuGet packages used by this
19-
repo's CI (it requires a full WDK install). Disable the post-build API
20-
validation step so that builds using the WindowsApplicationForDrivers10.0
21-
toolset (e.g. ApplicationForDriversTestTemplate) can succeed without it.
22-
The validation is irrelevant for the CodeQL analysis we are performing.
23-
-->
24-
<ApiValidatorEnabled>false</ApiValidatorEnabled>
2517
</PropertyGroup>
2618
<Import Project="$(WdkNuGetPackagesDir)Microsoft.Windows.WDK.x64.10.0.26100.6584\build\native\Microsoft.Windows.WDK.x64.props" Condition="Exists('$(WdkNuGetPackagesDir)Microsoft.Windows.WDK.x64.10.0.26100.6584\build\native\Microsoft.Windows.WDK.x64.props') and '$(Platform)' == 'x64'" />
2719
<Import Project="$(WdkNuGetPackagesDir)Microsoft.Windows.WDK.arm64.10.0.26100.6584\build\native\Microsoft.Windows.WDK.arm64.props" Condition="Exists('$(WdkNuGetPackagesDir)Microsoft.Windows.WDK.arm64.10.0.26100.6584\build\native\Microsoft.Windows.WDK.arm64.props') and '$(Platform)' == 'ARM64'" />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project>
2+
<!--
3+
Auto-imported by MSBuild AFTER each project's main targets (including the
4+
WDK targets pulled in by Directory.Build.props), for every project under
5+
src\drivers\test\.
6+
7+
Override the WDK's `ApiValidator` post-build target with an empty target.
8+
The WDK NuGet packages restored by the CI workflow do not ship the
9+
`ApiValidator.exe` binary even though they reference its path in
10+
`WindowsDriver.common.targets`. Without this override the target fails
11+
with:
12+
13+
error MSB3721: The command "...\ApiValidator.exe -DriverPackagePath:..."
14+
exited with code 1
15+
16+
breaking the `codeql database create` invocation for tests built with the
17+
Universal driver target platform (UnsafeCallInGlobalInit,
18+
MultithreadedAVCondition, StaticInitializer, DeviceInitApi, FloatSafeExit,
19+
FloatUnsafeExit, ...).
20+
21+
API validation is irrelevant to the CodeQL static analysis these tests
22+
perform, so replacing the target with a no-op is safe here. Setting
23+
`ApiValidatorExePath` to an empty value would still leave the target
24+
running an empty exec; replacing the target itself is the most reliable
25+
suppression.
26+
-->
27+
<Target Name="ApiValidator" />
28+
</Project>

src/drivers/test/build_create_analyze_test.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323

2424
print_mutex = threading.Lock()
25+
results_mutex = threading.Lock()
2526
health_df = pd.DataFrame()
2627
detailed_health_df = pd.DataFrame()
2728

@@ -830,6 +831,36 @@ def compare_health_results(curr_results_path):
830831
os.remove(prev_results)
831832
exit(0)
832833

834+
def _run_single_test(ql_test):
835+
"""
836+
Run a single ql_test and update the shared results DataFrames.
837+
Returns the test name on failure, or None on success. Designed to be
838+
safe to invoke concurrently from a thread pool: each ql_test uses its
839+
own working/, TestDB/, and AnalysisFiles/<name>.sarif paths, and shared
840+
DataFrame mutations are guarded by ``results_mutex``.
841+
"""
842+
result_sarif = run_test(ql_test)
843+
if args.build_database_only:
844+
return None
845+
if not result_sarif:
846+
with print_mutex:
847+
print("Error running test: " + ql_test.get_ql_name(), "Skipping...")
848+
return ql_test.get_ql_name()
849+
try:
850+
analysis_results, detailed_analysis_results = sarif_results(ql_test, result_sarif)
851+
except Exception as e:
852+
with print_mutex:
853+
print("Error reading sarif results for " + ql_test.get_ql_name() + ": " + str(e))
854+
return ql_test.get_ql_name()
855+
with results_mutex:
856+
health_df.at[ql_test.get_ql_name(), "Result"] = str(
857+
int(analysis_results['error']) +
858+
int(analysis_results['warning']) +
859+
int(analysis_results['note']))
860+
detailed_health_df.at[ql_test.get_ql_name(), "Result"] = str(detailed_analysis_results)
861+
return None
862+
863+
833864
def run_tests(ql_tests_dict):
834865
"""
835866
Run the given CodeQL tests.
@@ -842,20 +873,32 @@ def run_tests(ql_tests_dict):
842873
"""
843874
ql_tests_with_attributes = parse_attributes(ql_tests_dict)
844875

845-
total_tests = 0
876+
total_tests = len(ql_tests_with_attributes)
846877
failed_tests = []
847878

848-
for ql_test in ql_tests_with_attributes:
849-
total_tests += 1
850-
result_sarif = run_test(ql_test)
851-
if not args.build_database_only:
852-
if not result_sarif:
853-
print("Error running test: " + ql_test.get_ql_name(),"Skipping...")
854-
failed_tests.append(ql_test.get_ql_name())
855-
continue
856-
analysis_results, detailed_analysis_results = sarif_results(ql_test, result_sarif)
857-
health_df.at[ql_test.get_ql_name(), "Result"] = str(int(analysis_results['error'])+int(analysis_results['warning'])+int(analysis_results['note']))
858-
detailed_health_df.at[ql_test.get_ql_name(), "Result"] = str(detailed_analysis_results)
879+
# Each ql_test runs against its own isolated working/, TestDB/, and
880+
# AnalysisFiles/<name>.sarif paths, so it is safe to execute multiple
881+
# tests concurrently. ``--jobs`` controls the worker count; the default
882+
# of ``os.cpu_count()`` mirrors what `msbuild` and `codeql` do internally
883+
# for their own parallelism. Use jobs=1 to fall back to the legacy
884+
# sequential behaviour (useful when debugging a single test).
885+
jobs = max(1, args.jobs) if args.jobs is not None else (os.cpu_count() or 1)
886+
if jobs == 1:
887+
for ql_test in ql_tests_with_attributes:
888+
failed = _run_single_test(ql_test)
889+
if failed is not None:
890+
failed_tests.append(failed)
891+
else:
892+
with print_mutex:
893+
print("Running " + str(total_tests) + " tests with " + str(jobs) + " parallel workers")
894+
pool = ThreadPool(jobs)
895+
try:
896+
for failed in pool.imap_unordered(_run_single_test, ql_tests_with_attributes):
897+
if failed is not None:
898+
failed_tests.append(failed)
899+
finally:
900+
pool.close()
901+
pool.join()
859902

860903
# save results
861904
result_file = "functiontestresults.xlsx"
@@ -950,6 +993,7 @@ def find_sln_file(path):
950993
parser.add_argument('--compare_results_no_build',help='Compare results to previous run',type=str,required=False,)
951994
parser.add_argument('--codeql_path', help='Path to the codeql executable',type=str,required=False,)
952995
parser.add_argument('--build_database_only', help='Build database only',action='store_true',required=False,)
996+
parser.add_argument('-j', '--jobs', help='Number of tests to run in parallel (default: number of CPU cores). Use 1 to disable parallelism.', type=int, required=False)
953997
args = parser.parse_args()
954998

955999
if args.codeql_path:

0 commit comments

Comments
 (0)