From 72531aadd852fea25d0557e92167554a49c3db5a Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:18:44 -0300 Subject: [PATCH] Validate authentication scenarios and publishing in both reference modes Expand the dedicated harness with configuration precedence, cached failures, switch and runtime mismatch coverage, genuine local package validation, and trimmed/NativeAOT execution. Wire standard build targets and Linux/Windows CI jobs with focused regressions and documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/instructions/testing.instructions.md | 13 ++ BUILDGUIDE.md | 31 +++++ build.proj | 22 ++- eng/pipelines/dotnet-sqlclient-ci-core.yml | 7 + .../authentication-validation-stage.yml | 81 +++++++++++ .../tests/Build-Orchestration.Tests.ps1 | 99 +++++++++++++- .../tests/Pipeline-Arguments.Tests.ps1 | 31 +++++ src/Microsoft.Data.SqlClient.slnx | 1 + .../ConfigurationTest.csproj | 2 + .../ConfigurationTest/Program.cs | 126 +++++++++++++----- .../ConfigurationTest/bad-initializer.config | 8 ++ .../ConfigurationTest/bad-provider.config | 12 ++ .../ConfigurationTest/bad-unsupported.config | 12 ++ .../PublishTest/Program.cs | 30 +++++ .../PublishTest/PublishTest.csproj | 25 ++++ .../tests/AuthenticationTests/README.md | 39 ++++++ .../RunAuthenticationTests.ps1 | 115 +++++++++++++++- 17 files changed, 608 insertions(+), 46 deletions(-) create mode 100644 eng/pipelines/pr/stages/authentication-validation-stage.yml create mode 100644 src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-initializer.config create mode 100644 src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-provider.config create mode 100644 src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-unsupported.config create mode 100644 src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/Program.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/PublishTest.csproj create mode 100644 src/Microsoft.Data.SqlClient/tests/AuthenticationTests/README.md diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index fa4396f2b0..bc6d734c61 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -8,6 +8,7 @@ applyTo: "**/tests/**,**/*Test*.cs" ``` src/Microsoft.Data.SqlClient/tests/ ├── FunctionalTests/ # Tests without SQL Server dependency +├── AuthenticationTests/ # Fresh-process configuration/runtime and trim/AOT fixtures ├── ManualTests/ # Integration tests requiring SQL Server ├── PerformanceTests/ # Benchmark-style perf validation ├── StressTests/ # Long-running stress coverage @@ -20,6 +21,18 @@ src/Microsoft.Data.SqlClient/tests/ ## Test Categories +### Authentication executable scenarios + +Keep non-xUnit authentication configuration, DLL-replacement and publish consumers in +`src/Microsoft.Data.SqlClient/tests/AuthenticationTests/`, not inside Abstractions unit tests. +Use `build.proj -t:TestAuthentication` with explicit `ReferenceType=Project` or `Package`; +Package mode prepares real local sibling packages. Use an isolated `NUGET_PACKAGES` and +unique `PackageVersionSqlClient` to avoid stale family images. The focused configuration +and runtime-version targets remain available. `AuthenticationPublish=true` adds Linux +net10.0 linux-x64 trimmed/NativeAOT publishes and executions with warnings as errors. +Windows executes net462 scenarios; Linux cross-builds are not Windows runtime validation. +`BuildAuthenticationTests` is part of `BuildTests`; ordinary unit tests stay separate. + ### Unit Tests (`UnitTests/`) - Test individual components in isolation - No external dependencies (database, network) diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 9426d90755..0cccc58546 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -86,6 +86,34 @@ dotnet build build.proj -t:TestAuthenticationRuntimeVersions -p:Configuration=Re The same target accepts `ReferenceType=Package` and the package-preparation options above. The runner restores the original DLL even if a scenario fails. +`TestAuthentication` runs the complete configuration/discovery/precedence matrix, +invalid provider/initializer/unsupported-provider and cached-failure tracing checks, +disabled switches, and both runtime mismatch scenarios. Add `AuthenticationPublish=true` +on Linux to publish **and execute** linux-x64 trimmed and NativeAOT consumers with +compiler/linker warnings as errors. GCC and the NativeAOT prerequisites are required. + +```bash +dotnet build build.proj -t:TestAuthentication -p:Configuration=Release -p:TestFramework=net10.0 -p:ReferenceType=Project -p:AuthenticationPublish=true -p:PackageVersionSqlClient=8.0.0-stack.6 +dotnet build build.proj -t:TestAuthentication -p:Configuration=Release -p:TestFramework=net10.0 -p:ReferenceType=Package -p:AuthenticationPublish=true -p:PackageVersionSqlClient=8.0.0-stack.6 +``` + +The Package target prepares Logging, Abstractions, SqlServer, SqlClient and Azure +through the normal pack targets. The fixtures use conditional package references, +verify local family packages exist and inspect restored assets; Package mode never +falls back to project references. Use a unique family version and isolated +`NUGET_PACKAGES` for each changed package image. Build number, suffix, file/package +versions, configuration and reference mode flow to all fixtures; the runner freezes +the computed family version before building, so JIT runtime version validation agrees +with the package graph. + +`BuildAuthenticationTests` builds scenario fixtures without executing them and is +included in `BuildTests`. All three fixtures are in the solution. The focused targets +remain available; ordinary xUnit tests are unchanged. See the +[authentication harness README](src/Microsoft.Data.SqlClient/tests/AuthenticationTests/README.md). +The authentication CI stage independently builds and runs Project **and** Package jobs +on Linux (net10.0, trim/AOT) and Windows (net462), with isolated caches and no assumed +artifact downloads. + ### Building Projects From the root of your repository, run `dotnet build` against `build.proj` with a build target, following this pattern: @@ -214,6 +242,9 @@ dotnet build -t: [optional_parameters] | `Test` | Runs the driver and extension test targets. Without `TestFramework`, all declared frameworks are attempted, including .NET Framework. _This takes considerable time and requires configured servers_. | | `TestAbstractions` | Runs all tests for Microsoft.Data.SqlClient.Extensions.Abstractions | | `TestAuthenticationConfiguration` | Runs the dedicated current/legacy app.config compatibility scenarios without SQL Server. | +| `TestAuthenticationRuntimeVersions` | Adds runtime DLL replacement checks with switches enabled and disabled. | +| `TestAuthentication` | Runs all authentication executable scenarios; optionally publishes and executes trimmed/NativeAOT consumers with `AuthenticationPublish=true`. | +| `BuildAuthenticationTests` | Builds the scenario fixtures without executing them; included in `BuildTests`. | | `TestAkvProvider` | Runs the unit test project for Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider. | | `TestAzure` | Runs all tests for Microsoft.Data.SqlClient.Extensions.Azure | | `TestSqlClient` | Runs the unit, functional, and manual test projects for Microsoft.Data.SqlClient. | diff --git a/build.proj b/build.proj index 864c6297eb..c3af76ff46 100644 --- a/build.proj +++ b/build.proj @@ -616,14 +616,28 @@ $(TestFramework) net10.0 -PackageVersionSqlClient "$(PackageVersionSqlClient)" - PackSqlClient + -FileVersionSqlClient "$(FileVersionSqlClient)" + -PackageVersionSqlServer "$(PackageVersionSqlServer)" + -BuildNumber "$(BuildNumber)" + -BuildSuffix "$(BuildSuffix)" + -Publish + "$(DotnetPath)dotnet" tool run pwsh -- -NonInteractive -NoProfile -File "$(AuthenticationTestsScript)" -Framework $(AuthenticationTestFramework) -ReferenceType $(ReferenceType) -Configuration $(Configuration) $(AuthenticationPackageVersionArgument) $(AuthenticationFileVersionArgument) $(AuthenticationSqlServerVersionArgument) $(AuthenticationBuildNumberArgument) $(AuthenticationBuildSuffixArgument) + PackSqlClient;PackAzure - - + + + + + + @@ -1396,7 +1410,7 @@ - + diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 5ee009d572..58529dd5f0 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -162,6 +162,13 @@ variables: value: SqlServer.Artifacts stages: + - template: /eng/pipelines/pr/stages/authentication-validation-stage.yml@self + parameters: + poolName: ${{ parameters.defaultPoolName }} + linuxPoolImage: ADO-UB24 + windowsPoolImage: ADO-Win25 + buildConfiguration: ${{ parameters.buildConfiguration }} + # Compute all package versions up front. Build and test stages consume the # compute_versions_ci outputs directly, so no version parameters are passed here. - template: /eng/pipelines/stages/compute-versions-ci-stage.yml@self diff --git a/eng/pipelines/pr/stages/authentication-validation-stage.yml b/eng/pipelines/pr/stages/authentication-validation-stage.yml new file mode 100644 index 0000000000..ccac3ae465 --- /dev/null +++ b/eng/pipelines/pr/stages/authentication-validation-stage.yml @@ -0,0 +1,81 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +parameters: + - name: poolName + type: string + - name: linuxPoolImage + type: string + - name: windowsPoolImage + type: string + - name: referenceTypes + type: object + default: [Project, Package] + - name: buildConfiguration + type: string + values: [Debug, Release] + +stages: + - stage: test_authentication + displayName: Authentication configuration and publishing + # Each job builds its own inputs through standard targets; no artifact download is assumed. + dependsOn: [] + jobs: + - ${{ each referenceType in parameters.referenceTypes }}: + - job: authentication_linux_${{ referenceType }} + displayName: Authentication Linux ${{ referenceType }} + timeoutInMinutes: 90 + pool: + name: ${{ parameters.poolName }} + demands: + - imageOverride -equals ${{ parameters.linuxPoolImage }} + variables: + NUGET_PACKAGES: $(Pipeline.Workspace)/authentication-linux-${{ referenceType }}/nuget + steps: + - template: /eng/pipelines/common/steps/install-dotnet.yml@self + parameters: + runtimes: [10.x] + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self + - task: DotNetCoreCLI@2 + displayName: Scenarios, runtime mismatch, trimmed and NativeAOT execution + inputs: + command: custom + custom: build + workingDirectory: $(Build.SourcesDirectory) + arguments: >- + build.proj -t:TestAuthentication + -p:ReferenceType=${{ referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:TestFramework=net10.0 + -p:PackageVersionSqlClient=8.0.0-auth.$(Build.BuildId) + -p:AuthenticationPublish=true + + - job: authentication_windows_${{ referenceType }} + displayName: Authentication Windows ${{ referenceType }} + timeoutInMinutes: 90 + pool: + name: ${{ parameters.poolName }} + demands: + - imageOverride -equals ${{ parameters.windowsPoolImage }} + variables: + NUGET_PACKAGES: $(Pipeline.Workspace)/authentication-windows-${{ referenceType }}/nuget + steps: + - template: /eng/pipelines/common/steps/install-dotnet.yml@self + parameters: + runtimes: [10.x] + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self + - task: DotNetCoreCLI@2 + displayName: .NET Framework scenarios and runtime mismatch execution + inputs: + command: custom + custom: build + workingDirectory: $(Build.SourcesDirectory) + arguments: >- + build.proj -t:TestAuthentication + -p:ReferenceType=${{ referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:TestFramework=net462 + -p:PackageVersionSqlClient=8.0.0-auth.$(Build.BuildId) diff --git a/eng/pipelines/scripts/tests/Build-Orchestration.Tests.ps1 b/eng/pipelines/scripts/tests/Build-Orchestration.Tests.ps1 index 3a12367828..41067b0ae2 100644 --- a/eng/pipelines/scripts/tests/Build-Orchestration.Tests.ps1 +++ b/eng/pipelines/scripts/tests/Build-Orchestration.Tests.ps1 @@ -50,7 +50,7 @@ printf 'ARG:%s\n' "$@" $output = & $dotnet msbuild (Join-Path $repoRoot $Project) -nologo @Properties ` -getProperty:TargetFramework,TargetFrameworks,_SqlClientPackageTfm ` - -getItem:PackageReference,PackageVersion,Reference 2>&1 + -getItem:PackageReference,PackageVersion,Reference,ProjectReference 2>&1 if ($LASTEXITCODE -ne 0) { throw "Project evaluation failed for ${Project}:`n$($output -join [Environment]::NewLine)" } @@ -146,6 +146,91 @@ printf 'ARG:%s\n' "$@" } } +Describe 'authentication scenario orchestration' { + It 'keeps the complete harness together and preserves warnings-as-errors and binding version checks' { + $fixtureHome = Join-Path $repoRoot 'src/Microsoft.Data.SqlClient/tests/AuthenticationTests' + foreach ($config in @('app.config', 'legacy.config', 'bad-provider.config', 'bad-initializer.config', 'bad-unsupported.config')) { + Test-Path (Join-Path $fixtureHome "ConfigurationTest/$config") | Should -BeTrue + } + $runner = Get-Content (Join-Path $fixtureHome 'RunAuthenticationTests.ps1') -Raw + $runner | Should -Match "'bad-provider', 'bad-initializer', 'bad-unsupported', 'disabled'" + $runner | Should -Match "'bad-runtime', 'bad-runtime-disabled'" + $runner | Should -Match 'Assert-PackageAssets' + $runner | Should -Match 'metadata.source' + $runner | Should -Match 'getProperty:SqlClientPackageVersion' + $runner | Should -Match 'SqlClientPackageVersion=\$version' + foreach ($gate in @('TreatWarningsAsErrors', 'ILLinkTreatWarningsAsErrors', 'IlcTreatWarningsAsErrors')) { + $runner | Should -Match "-p:${gate}=true" + } + [xml]$replacement = Get-Content (Join-Path $fixtureHome 'RuntimeVersionTest/RuntimeVersionTest.csproj') -Raw + $replacement.Project.PropertyGroup.AssemblyVersion | Should -Be '$(SqlClientAssemblyVersion)' + Test-Path (Join-Path $fixtureHome 'PublishTest/PublishTest.csproj') | Should -BeTrue + } + + It 'forwards mode, configuration and all version inputs to ' -ForEach @( + @{ Target = 'TestAuthenticationConfiguration'; Switch = $null } + @{ Target = 'TestAuthenticationRuntimeVersions'; Switch = '-RuntimeVersions' } + @{ Target = 'TestAuthentication'; Switch = '-AllScenarios' } + @{ Target = 'BuildAuthenticationTests'; Switch = '-BuildOnly' } + ) { + $arguments = Get-ChildArguments $Target @( + '-p:ReferenceType=Package', '-p:Configuration=Debug', '-p:TestFramework=net462', + '-p:SkipDependencyPack=true', '-p:PackageVersionSqlClient=8.0.0-probe.6', + '-p:FileVersionSqlClient=8.0.0.6', '-p:PackageVersionSqlServer=1.0.0-probe', + '-p:BuildNumber=123.4', '-p:BuildSuffix=probe', '-p:AuthenticationPublish=true' + ) + $script = $arguments[([array]::IndexOf($arguments, '-File') + 1)] + [System.IO.Path]::GetFullPath($script) | Should -Be "$repoRoot/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RunAuthenticationTests.ps1" + foreach ($value in @('Package', 'Debug', 'net462', '8.0.0-probe.6', '8.0.0.6', '1.0.0-probe', '123.4', 'probe')) { + $arguments | Should -Contain $value + } + if ($Switch) { $arguments | Should -Contain $Switch } + if ($Target -eq 'TestAuthentication') { + $arguments | Should -Contain '-RuntimeVersions' + $arguments | Should -Contain '-Publish' + } else { + $arguments | Should -Not -Contain '-Publish' + } + } + + It 'prepares local driver and Azure packages only in Package mode' { + [xml]$build = Get-Content $buildProject -Raw + $group = @($build.Project.PropertyGroup.TestAuthenticationConfigurationDependsOn | Where-Object { $_ })[0] + $group.Condition | Should -Match "'\$\(ReferenceType\)' == 'Package'" + $group.InnerText | Should -Be 'PackSqlClient;PackAzure' + foreach ($target in @('TestAuthentication', 'TestAuthenticationConfiguration', 'TestAuthenticationRuntimeVersions', 'BuildAuthenticationTests')) { + @($build.Project.Target | Where-Object Name -eq $target)[0].DependsOnTargets | + Should -Be '$(TestAuthenticationConfigurationDependsOn)' + } + @($build.Project.Target | Where-Object Name -eq 'BuildTests')[0].DependsOnTargets | + Should -Match 'BuildAuthenticationTests' + } + + It ' uses actual references with the computed family version' -ForEach @( + foreach ($fixture in @('ConfigurationTest', 'PublishTest')) { + foreach ($mode in @('Project', 'Package')) { + @{ Fixture = $fixture; Mode = $mode } + } + } + ) { + $result = Get-ProjectEvaluation "src/Microsoft.Data.SqlClient/tests/AuthenticationTests/$Fixture/$Fixture.csproj" @( + "-p:ReferenceType=$Mode", '-p:TargetFramework=net10.0', + '-p:BuildNumber=123.4', '-p:BuildSuffix=probe' + ) + $familyProjects = @($result.Items.ProjectReference | Where-Object { $_.Identity -match 'SqlClient' }) + $familyPackages = @($result.Items.PackageReference | Where-Object { $_.Identity -match '^Microsoft.Data.SqlClient' }) + if ($Mode -eq 'Project') { + $familyProjects.Count | Should -Be 2 + $familyPackages.Count | Should -Be 0 + } else { + $familyProjects.Count | Should -Be 0 + $familyPackages.Count | Should -Be 2 + @($result.Items.PackageVersion | Where-Object { $_.Identity -eq 'Microsoft.Data.SqlClient.Extensions.Azure' })[0].Version | + Should -Match 'probe\.123\.4' + } + } +} + Describe 'supported target frameworks' { # Pipeline matrices and standalone VM installers must agree: a clean benchmark VM # should not download retired runtimes just because a script bypasses YAML setup. @@ -217,7 +302,6 @@ Describe 'supported target frameworks' { Frameworks = 'netstandard2.0' Projects = @( 'src\Microsoft.Data.SqlClient.Internal\Logging\src\Logging.csproj' - 'src\Microsoft.Data.SqlClient.Extensions\Abstractions\src\Abstractions.csproj' 'src\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider\src\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj' 'src\Microsoft.Data.SqlClient\tests\tools\Microsoft.Data.SqlClient.TestUtilities\Microsoft.Data.SqlClient.TestUtilities.csproj' 'src\Microsoft.Data.SqlClient\tests\tools\TDS\TDS\TDS.csproj' @@ -226,6 +310,12 @@ Describe 'supported target frameworks' { 'tools\GenAPI\Microsoft.Cci.Extensions\Microsoft.Cci.Extensions.csproj' ) } + @{ + Frameworks = 'netstandard2.0;net10.0' + Projects = @( + 'src\Microsoft.Data.SqlClient.Extensions\Abstractions\src\Abstractions.csproj' + ) + } @{ Frameworks = 'net10.0' Projects = @( @@ -676,6 +766,7 @@ Describe 'local package version freshness' { net10.0 $(MSBuildThisFileDirectory)cache + false @@ -756,10 +847,12 @@ Describe 'SqlClient build helper path quoting' { 'src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj' ` @('_CheckPwshToolRestored', 'TrimDocsForIntelliSense') ` @("-p:RepoRoot=$repoRoot/", "-p:DocumentationFile=$documentation", + "-p:TrimDocsScript=$repoRoot/tools/intellisense/TrimDocs.ps1", '-p:GenerateDocumentationFile=true') $arguments | Should -Contain '-File' - $arguments | Should -Contain "$repoRoot/tools/intellisense/TrimDocs.ps1" + $script = $arguments[([array]::IndexOf($arguments, '-File') + 1)] + [System.IO.Path]::GetFullPath($script.Replace('\', '/')) | Should -Be "$repoRoot/tools/intellisense/TrimDocs.ps1" $arguments | Should -Contain ([System.IO.Path]::GetFullPath($documentation)) } diff --git a/eng/pipelines/scripts/tests/Pipeline-Arguments.Tests.ps1 b/eng/pipelines/scripts/tests/Pipeline-Arguments.Tests.ps1 index 2be8a65784..047ec02e83 100644 --- a/eng/pipelines/scripts/tests/Pipeline-Arguments.Tests.ps1 +++ b/eng/pipelines/scripts/tests/Pipeline-Arguments.Tests.ps1 @@ -59,4 +59,35 @@ Describe 'CI dotnet argument variables in ' -ForEach @( ($template -match '(?m)^\s*-\s*name:\s*dotnetBuildOpts\s*$') | Should -BeTrue ([regex]::Matches($template, '\$\(dotnetBuildOpts\)')).Count | Should -Be 5 } + + Describe 'authentication CI matrix' { + BeforeAll { + $pipelineRoot = Join-Path $PSScriptRoot '../..' + $template = Get-Content (Join-Path $pipelineRoot 'pr/stages/authentication-validation-stage.yml') -Raw + $core = Get-Content (Join-Path $pipelineRoot 'dotnet-sqlclient-ci-core.yml') -Raw + } + + It 'runs both modes on Linux and Windows through standard package-preparing targets' { + $template | Should -Match 'default: \[Project, Package\]' + $template | Should -Match 'each referenceType in parameters.referenceTypes' + ([regex]::Matches($template, 'build.proj -t:TestAuthentication')).Count | Should -Be 2 + ([regex]::Matches($template, '-p:ReferenceType=\$\{\{ referenceType \}\}')).Count | Should -Be 2 + $template | Should -Match '-p:TestFramework=net10.0' + $template | Should -Match '-p:TestFramework=net462' + ([regex]::Matches($template, '-p:AuthenticationPublish=true')).Count | Should -Be 1 + $template | Should -Not -Match 'SkipDependencyPack|download:|Abstractions/test/RunAuthenticationTests' + $template | Should -Not -Match '(?i)(run|build|test)Arguments' + } + + It 'isolates caches and versions and passes the pool and configuration parameters' { + $template | Should -Match 'authentication-linux-\$\{\{ referenceType \}\}/nuget' + $template | Should -Match 'authentication-windows-\$\{\{ referenceType \}\}/nuget' + ([regex]::Matches($template, '-p:PackageVersionSqlClient=8.0.0-auth.\$\(Build.BuildId\)')).Count | Should -Be 2 + $core | Should -Match '/eng/pipelines/pr/stages/authentication-validation-stage.yml@self' + $core | Should -Match 'poolName: \$\{\{ parameters.defaultPoolName \}\}' + $core | Should -Match 'linuxPoolImage: ADO-UB24' + $core | Should -Match 'windowsPoolImage: ADO-Win25' + $core | Should -Match 'buildConfiguration: \$\{\{ parameters.buildConfiguration \}\}' + } + } } diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index 99a6e834ed..e5db509836 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -205,6 +205,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/ConfigurationTest.csproj b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/ConfigurationTest.csproj index 9b8b7bea80..d974b39698 100644 --- a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/ConfigurationTest.csproj +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/ConfigurationTest.csproj @@ -9,9 +9,11 @@ + + diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/Program.cs b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/Program.cs index af0f9c0792..be3f9ec441 100644 --- a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/Program.cs +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/Program.cs @@ -8,19 +8,22 @@ using System.Diagnostics.Tracing; string scenario = args.Single(); -if (scenario is "bad-runtime" or "bad-runtime-disabled") +if (scenario == "disabled" || scenario == "bad-runtime-disabled") { - if (scenario == "bad-runtime-disabled") + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.EnableAppConfig", false); + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.EnableAzureExtensionDiscovery", false); +} +if (scenario.StartsWith("bad-", StringComparison.Ordinal)) +{ + if (scenario is "bad-provider" or "bad-runtime-disabled") { - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.EnableAppConfig", false); - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.EnableAzureExtensionDiscovery", false); Require(!SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, new TestProvider()), - "Disabling configuration and discovery bypassed runtime version enforcement."); + "Failed bootstrap did not return false."); } else { Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault) is null, - "Mismatched driver bootstrap returned a provider."); + "Failed bootstrap did not return null."); } using var listener = new AuthenticationTraceListener(); @@ -28,37 +31,78 @@ "Cached version failure did not return null."); Require(!SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, new TestProvider()), "Cached version failure did not return false."); - Require(listener.Messages.Any(message => message.Contains("version mismatch") && - message.Contains("7.0.1") && message.Contains("Upgrade all SqlClient family packages together")), - "Cached runtime mismatch diagnostics omitted the exact version or corrective guidance."); - Require(listener.Messages.Any(message => message.Contains("GetProvider") && message.Contains("restart")) && - listener.Messages.Any(message => message.Contains("SetProvider") && message.Contains("restart")), - "Cached version failures did not diagnose both registry entry points."); + string expected = scenario switch + { + "bad-provider" => "MissingProvider", + "bad-initializer" => "MissingInitializer", + "bad-unsupported" => "UnsupportedProvider", + _ => "version mismatch" + }; + Require(listener.Messages.Any(message => message.Contains(expected)), + "Cached failure diagnostics omitted the failing type or version."); + if (scenario.StartsWith("bad-runtime", StringComparison.Ordinal)) + { + Require(listener.Messages.Any(message => message.Contains("7.0.1") && + message.Contains("Upgrade all SqlClient family packages together")), + "Cached runtime mismatch diagnostics omitted the exact version or corrective guidance."); + } + Require(listener.Messages.Any(message => message.Contains("GetProvider") && + message.Contains("ActiveDirectoryDefault") && message.Contains("returning null") && message.Contains("restart")) && + listener.Messages.Any(message => message.Contains("SetProvider") && + message.Contains("ActiveDirectoryDefault") && message.Contains("returning false") && message.Contains("restart")), + "Cached failures did not diagnose both registry entry points."); Console.WriteLine("PASS: " + scenario); return; } -string sectionName = scenario switch +if (scenario == "disabled") { - "configured" => "SqlClientAuthenticationProviders", - "legacy" => "SqlAuthenticationProviders", - _ => throw new ArgumentException("Unknown configuration scenario: " + scenario) -}; + Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault) is null, + "Discovery switch was ignored."); + Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive) is null, + "Configuration switch was ignored."); + Require(!TestInitializer.Initialized, "Disabled initializer ran."); +} +else +{ + string sectionName = scenario switch + { + "configured" => "SqlClientAuthenticationProviders", + "legacy" => "SqlAuthenticationProviders", + _ => throw new ArgumentException("Unknown configuration scenario: " + scenario) + }; -var section = ConfigurationManager.GetSection(sectionName) as SqlAuthenticationProviderConfigurationSection - ?? throw new InvalidOperationException("The section declaration did not resolve."); -ConfigurationSection configurationSection = section; -Require(configurationSection.ElementInformation.Properties.Count == 4 && section.Providers.Count == 1, - "The public configuration schema did not compile or load through its transitive dependencies."); -Require(section.GetType().Assembly.GetName().Name == "Microsoft.Data.SqlClient.Extensions.Abstractions", - "The assembly-qualified handler was not forwarded to Abstractions."); -Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive) is TestProvider, - "The Abstractions registry did not load the configured provider."); -Require(TestInitializer.Initialized, "The configured initializer did not run."); -Require(typeof(SqlAuthenticationInitializer).Assembly.GetName().Name == "Microsoft.Data.SqlClient.Extensions.Abstractions", - "The initializer type did not relocate to Abstractions."); -Require(!SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, new TestProvider()), - "Application registration replaced the configured provider."); + var section = ConfigurationManager.GetSection(sectionName) as SqlAuthenticationProviderConfigurationSection + ?? throw new InvalidOperationException("The section declaration did not resolve."); + ConfigurationSection configurationSection = section; + Require(configurationSection.ElementInformation.Properties.Count == 4 && section.Providers.Count == 1, + "The public configuration schema did not compile or load through its transitive dependencies."); + Require(section.GetType().Assembly.GetName().Name == "Microsoft.Data.SqlClient.Extensions.Abstractions", + "The assembly-qualified handler was not forwarded to Abstractions."); + using var precedenceListener = new AuthenticationTraceListener(); + Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault) + is ActiveDirectoryAuthenticationProvider, "First access did not discover Azure."); + Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive)?.GetType() == typeof(TestProvider), + "Configuration did not replace the initializer provider."); + Require(TestInitializer.Initialized, "The configured initializer did not run."); + Require(typeof(SqlAuthenticationInitializer).Assembly.GetName().Name == "Microsoft.Data.SqlClient.Extensions.Abstractions", + "The initializer type did not relocate to Abstractions."); + Require(!SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, new TestProvider()), + "Application registration replaced the configured provider."); + Require(SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) is InitializerProvider, + "Azure replaced the initializer provider."); + Require(!SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, new TestProvider()), + "Application registration replaced the Config-tier initializer."); + Require(precedenceListener.Messages.Any(message => message.Contains("ActiveDirectoryInteractive") && + message.Contains("update app.config or its initializer")), + "Configuration precedence refusal did not explain how to change the provider."); +} +var applicationProvider = new TestProvider(); +Require(SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, applicationProvider), + "Application registration did not replace the discovered provider."); +Require(ReferenceEquals(applicationProvider, + SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault)), + "Application registration did not round-trip."); Console.WriteLine("PASS: " + scenario); /// Fails the executable scenario when its required compatibility contract is not preserved. @@ -76,11 +120,17 @@ public sealed class TestInitializer : SqlAuthenticationInitializer public static bool Initialized; /// - public override void Initialize() => Initialized = true; + public override void Initialize() + { + Initialized = SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, new InitializerProvider()) && + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive, new InitializerProvider()); + } } /// Registers a provider without contacting an identity service or a database. -public sealed class TestProvider : SqlAuthenticationProvider +public class TestProvider : SqlAuthenticationProvider { /// public override bool IsSupported(SqlAuthenticationMethod method) => true; @@ -90,6 +140,16 @@ public override Task AcquireTokenAsync(SqlAuthentication throw new NotSupportedException(); } +/// Distinguishes initializer registration from configuration and application registration. +public sealed class InitializerProvider : TestProvider { } + +/// Exercises configuration bootstrap failure for an unsupported method. +public sealed class UnsupportedProvider : TestProvider +{ + /// + public override bool IsSupported(SqlAuthenticationMethod method) => false; +} + /// Captures diagnostics when tracing is enabled after the original bootstrap failure. internal sealed class AuthenticationTraceListener : EventListener { diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-initializer.config b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-initializer.config new file mode 100644 index 0000000000..937340672e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-initializer.config @@ -0,0 +1,8 @@ + + + +
+ + + diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-provider.config b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-provider.config new file mode 100644 index 0000000000..ef6452f5dd --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-provider.config @@ -0,0 +1,12 @@ + + + +
+ + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-unsupported.config b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-unsupported.config new file mode 100644 index 0000000000..8d0643c59e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/ConfigurationTest/bad-unsupported.config @@ -0,0 +1,12 @@ + + + +
+ + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/Program.cs b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/Program.cs new file mode 100644 index 0000000000..491c2cc9b8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/Program.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Data.SqlClient; + +if (SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault) is not null) +{ + throw new InvalidOperationException("Trimmed/AOT discovery must not install an Azure provider."); +} +var provider = new TestProvider(); +if (!SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, provider) || + !ReferenceEquals(provider, + SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault))) +{ + throw new InvalidOperationException("Explicit provider registration did not round-trip."); +} +if (AppDomain.CurrentDomain.GetAssemblies().Any(a => + a.GetName().Name == "Microsoft.Data.SqlClient.Extensions.Azure")) +{ + throw new InvalidOperationException("Azure discovery loaded the optional extension."); +} +Console.WriteLine("PASS: discovery absent; explicit registration succeeds."); + +internal sealed class TestProvider : SqlAuthenticationProvider +{ + public override bool IsSupported(SqlAuthenticationMethod method) => true; + public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => + throw new NotSupportedException(); +} diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/PublishTest.csproj b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/PublishTest.csproj new file mode 100644 index 0000000000..629f8c2890 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/PublishTest/PublishTest.csproj @@ -0,0 +1,25 @@ + + + Exe + net10.0 + enable + enable + false + false + true + true + true + true + true + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/README.md b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/README.md new file mode 100644 index 0000000000..4e40fe9fc9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/README.md @@ -0,0 +1,39 @@ +# Authentication scenarios + +These executable fixtures are intentionally separate from ordinary xUnit tests. +They require neither SQL Server nor identity-service access. Each scenario runs +in a fresh process so bootstrap state, switches and app.config cannot leak. + +Use `build.proj` from the repository root: + +```bash +dotnet build build.proj -t:TestAuthentication -p:Configuration=Release -p:TestFramework=net10.0 -p:ReferenceType=Project -p:AuthenticationPublish=true -p:PackageVersionSqlClient=8.0.0-stack.6 +dotnet build build.proj -t:TestAuthentication -p:Configuration=Release -p:TestFramework=net10.0 -p:ReferenceType=Package -p:AuthenticationPublish=true -p:PackageVersionSqlClient=8.0.0-stack.6 +``` + +Set `NUGET_PACKAGES` to an isolated cache and choose a new family version when +rebuilding a different package image. Package mode prepares real local Logging, +Abstractions, SqlServer, SqlClient and Azure packages using standard orchestration; +fixtures reject missing local family packages and verify their restored package assets. +Only use `SkipDependencyPack=true` with already prepared packages of that exact version. +The runner forwards configuration, reference mode, build number/suffix and family/ +SqlServer version inputs and freezes the effective family version for all builds. + +| Fixture | Checks | +| --- | --- | +| ConfigurationTest | Current and legacy real app.config handlers still compile through the public `ConfigurationSection` base type and resolve through driver type forwards; initializer/configuration beat discovered Azure and application registration; application registration replaces Azure; invalid provider/initializer/unsupported provider failures remain cached and traced after tracing is enabled; disabled switches leave explicit registration usable. | +| RuntimeVersionTest | Replacement driver retains `$(SqlClientAssemblyVersion)` but reports informational version 7.0.1. Both enabled and disabled bootstrap switches reject it; tracing after the first failure reports exact versions and upgrade/restart guidance. The runner restores the real driver and configuration in `finally` blocks. | +| PublishTest | References Abstractions and Azure but never uses Azure types in source. linux-x64 trim/AOT publishes treat C#, ILLink and ILC warnings as errors. Execution proves discovery absent and explicit registration succeeds; the runner verifies no Azure DLL or Azure runtime dependency remains. XML documentation is not a runtime assembly. | + +The focused `TestAuthenticationConfiguration` target runs just current/legacy +configuration; `TestAuthenticationRuntimeVersions` adds the replacement checks. +`TestAuthentication` runs six configuration and two runtime scenarios. Optional +`AuthenticationPublish=true` adds two publishes and executions. + +On Windows use `TestFramework=net462` and omit publishing. Linux may cross-build +net462 using `BuildAuthenticationTests`, but **cannot execute its Windows runtime +checks**. `BuildTests` includes `BuildAuthenticationTests`; the solution includes +all three fixtures. Direct runner invocation requires packages prepared in advance. + +The CI stage runs both reference modes on Linux and Windows, self-building its +packages with per-job caches and unique versions rather than assuming artifacts. diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RunAuthenticationTests.ps1 b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RunAuthenticationTests.ps1 index 3b064c1c5f..a8bbeccd0a 100644 --- a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RunAuthenticationTests.ps1 +++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RunAuthenticationTests.ps1 @@ -10,16 +10,26 @@ param( [ValidateSet('Debug', 'Release')] [string]$Configuration = 'Release', [string]$PackageVersionSqlClient, - [switch]$RuntimeVersions + [string]$FileVersionSqlClient, + [string]$PackageVersionSqlServer, + [string]$BuildNumber, + [string]$BuildSuffix, + [switch]$RuntimeVersions, + [switch]$AllScenarios, + [switch]$Publish, + [switch]$BuildOnly ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest -if ($Framework -eq 'net462' -and ![System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( +if (!$BuildOnly -and $Framework -eq 'net462' -and ![System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( [System.Runtime.InteropServices.OSPlatform]::Windows)) { throw 'Executing net462 authentication scenarios requires Windows; cross-building is not runtime validation.' } +if ($Publish -and ($Framework -ne 'net10.0' -or !$IsLinux -or $BuildOnly)) { + throw 'Trimmed/NativeAOT execution requires net10.0 on Linux, without BuildOnly.' +} function Invoke-DotNet { param([string[]]$CommandArguments) @@ -31,18 +41,74 @@ function Invoke-DotNet { $project = Join-Path $PSScriptRoot 'ConfigurationTest/ConfigurationTest.csproj' $buildOptions = @("-p:ReferenceType=$ReferenceType") -if ($PackageVersionSqlClient) { - $buildOptions += "-p:SqlClientPackageVersion=$PackageVersionSqlClient" +foreach ($property in @( + @('SqlClientPackageVersion', $PackageVersionSqlClient), + @('SqlClientFileVersion', $FileVersionSqlClient), + @('SqlServerPackageVersion', $PackageVersionSqlServer), + @('BuildNumber', $BuildNumber), + @('BuildSuffix', $BuildSuffix) +)) { + if ($property[1]) { $buildOptions += "-p:$($property[0])=$($property[1])" } +} +# Freeze the effective family version, including build-number/suffix defaults, for every fixture. +$version = & dotnet msbuild $project -nologo '-getProperty:SqlClientPackageVersion' @buildOptions +if ($LASTEXITCODE -ne 0 -or !$version) { throw 'Failed to evaluate the authentication family version.' } +$version = "$version".Trim() +$buildOptions += "-p:SqlClientPackageVersion=$version" +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../../..')).Path +if ($ReferenceType -eq 'Package') { + foreach ($id in @('Microsoft.Data.SqlClient', 'Microsoft.Data.SqlClient.Extensions.Azure', + 'Microsoft.Data.SqlClient.Extensions.Abstractions', 'Microsoft.Data.SqlClient.Internal.Logging')) { + if (!(Test-Path (Join-Path $repoRoot "packages/$id.$version.nupkg"))) { + throw "Missing locally built $id $version. Use build.proj authentication targets to prepare packages." + } + } +} +function Assert-PackageAssets { + param([string]$Fixture) + if ($ReferenceType -ne 'Package') { return } + $assets = Get-Content (Join-Path $PSScriptRoot "$Fixture/obj/project.assets.json") -Raw | ConvertFrom-Json + foreach ($library in $assets.libraries.PSObject.Properties) { + if ($library.Name.Split('/')[0] -in @('Microsoft.Data.SqlClient', + 'Microsoft.Data.SqlClient.Extensions.Abstractions', 'Microsoft.Data.SqlClient.Extensions.Azure', + 'Microsoft.Data.SqlClient.Internal.Logging')) { + if ($library.Value.type -ne 'package' -or !$library.Name.EndsWith("/$version")) { + throw "Fixture $Fixture did not consume family package $version : $($library.Name)" + } + $packageFolder = @($assets.packageFolders.PSObject.Properties)[0].Name + $metadataPath = Join-Path $packageFolder "$($library.Value.path)/.nupkg.metadata" + $metadata = Get-Content $metadataPath -Raw | ConvertFrom-Json + if ([System.IO.Path]::GetFullPath($metadata.source).TrimEnd('/', '\') -ne + (Join-Path $repoRoot 'packages').TrimEnd('/', '\')) { + throw "Fixture $Fixture used a non-local family package: $($library.Name) from $($metadata.source)" + } + } + } } Invoke-DotNet -CommandArguments (@('build', $project, '-c', $Configuration, '-f', $Framework) + $buildOptions) +Assert-PackageAssets 'ConfigurationTest' +if ($BuildOnly) { + Invoke-DotNet -CommandArguments (@('build', + (Join-Path $PSScriptRoot 'RuntimeVersionTest/RuntimeVersionTest.csproj'), + '-c', $Configuration, '-f', $Framework) + $buildOptions) + if ($Framework -eq 'net10.0') { + Invoke-DotNet -CommandArguments (@('build', + (Join-Path $PSScriptRoot 'PublishTest/PublishTest.csproj'), + '-c', $Configuration, '-f', $Framework) + $buildOptions) + Assert-PackageAssets 'PublishTest' + } + return +} $output = Join-Path $PSScriptRoot "ConfigurationTest/bin/$Configuration/$Framework" $extension = if ($Framework -eq 'net462') { 'exe' } else { 'dll' } $app = Join-Path $output "AuthenticationConfigurationTest.$extension" $configPath = "$app.config" $originalConfig = [System.IO.File]::ReadAllBytes($configPath) try { - foreach ($scenario in @('configured', 'legacy')) { - $config = if ($scenario -eq 'configured') { 'app.config' } else { 'legacy.config' } + $scenarios = @('configured', 'legacy') + if ($AllScenarios) { $scenarios += @('bad-provider', 'bad-initializer', 'bad-unsupported', 'disabled') } + foreach ($scenario in $scenarios) { + $config = if ($scenario -in @('configured', 'disabled')) { 'app.config' } else { "$scenario.config" } Copy-Item -LiteralPath (Join-Path $PSScriptRoot "ConfigurationTest/$config") -Destination $configPath -Force if ($Framework -eq 'net462') { & $app $scenario @@ -77,3 +143,40 @@ if ($RuntimeVersions) { Remove-Item -LiteralPath $backup } } + +if ($Publish) { + $publishProject = Join-Path $PSScriptRoot 'PublishTest/PublishTest.csproj' + foreach ($mode in @('trim', 'aot')) { + $publishOutput = Join-Path $PSScriptRoot "PublishTest/bin/$ReferenceType-$Configuration/publish-$mode" + # A clean destination prevents an earlier untrimmed Azure DLL from invalidating the proof. + if (Test-Path $publishOutput) { Remove-Item $publishOutput -Recurse -Force } + Invoke-DotNet -CommandArguments (@('publish', $publishProject, '-c', $Configuration, '-r', 'linux-x64', + "-p:PublishMode=$mode", '-p:CppCompilerAndLinker=gcc', '-p:StripSymbols=false', + '-p:TreatWarningsAsErrors=true', '-p:ILLinkTreatWarningsAsErrors=true', + '-p:IlcTreatWarningsAsErrors=true', '-o', $publishOutput) + $buildOptions) + Assert-PackageAssets 'PublishTest' + if (Get-ChildItem $publishOutput -Filter 'Microsoft.Data.SqlClient.Extensions.Azure.dll') { + throw "$mode retained the optional Azure assembly." + } + $deps = Join-Path $publishOutput 'PublishTest.deps.json' + if (Test-Path $deps) { + $dependencyGraph = Get-Content $deps -Raw | ConvertFrom-Json + # Package metadata can remain after trimming; only executable runtime assets matter. + foreach ($target in $dependencyGraph.targets.PSObject.Properties) { + foreach ($library in $target.Value.PSObject.Properties) { + if ($library.Name.StartsWith('Microsoft.Data.SqlClient.Extensions.Azure/')) { + foreach ($kind in @('runtime', 'runtimeTargets', 'native')) { + $assets = $library.Value.PSObject.Properties[$kind] + if ($assets -and @($assets.Value.PSObject.Properties).Count -gt 0) { + throw "$mode retained Azure runtime assets in the dependency graph." + } + } + } + } + } + } + & (Join-Path $publishOutput 'PublishTest') + if ($LASTEXITCODE -ne 0) { throw "$mode authentication test failed." } + Write-Host "PASS: $ReferenceType $mode Azure absent from published output." + } +}