From 1b254dfaa4bee50674eed7241e80c5390918b75a Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:06:23 -0300 Subject: [PATCH 1/3] - Added validation stage and symbol verification job. --- .github/copilot-instructions.md | 1 + .../templates/steps/override-sni-version.yml | 10 +- .../jobs/validate-signed-package-job.yml | 12 +- .../onebranch/jobs/validate-symbols-job.yml | 74 +++ .../onebranch/jobs/validate-symbols.ps1 | 171 +++++++ .../onebranch/sqlclient-non-official.yml | 15 +- .../onebranch/sqlclient-official.yml | 15 +- .../onebranch/stages/build-stages.yml | 41 +- .../{release-stages.yml => release-stage.yml} | 31 +- .../onebranch/stages/validation-stage.yml | 125 +++++ .../onebranch/steps/publish-symbols-step.yml | 60 +-- eng/pipelines/onebranch/tests/README.md | 77 +++ .../tests/validate-symbols.Tests.ps1 | 448 ++++++++++++++++++ 13 files changed, 1002 insertions(+), 78 deletions(-) create mode 100644 eng/pipelines/onebranch/jobs/validate-symbols-job.yml create mode 100644 eng/pipelines/onebranch/jobs/validate-symbols.ps1 rename eng/pipelines/onebranch/stages/{release-stages.yml => release-stage.yml} (90%) create mode 100644 eng/pipelines/onebranch/stages/validation-stage.yml create mode 100644 eng/pipelines/onebranch/tests/README.md create mode 100644 eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0c78f461cb..74a91da5ff 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -138,6 +138,7 @@ When a new issue is created, follow these steps: - All source code is in `src/Microsoft.Data.SqlClient/src/`. Do NOT add code to legacy `netfx/src/` or `netcore/src/` directories. - Only `ref/` folders in `netcore/ref/` and `netfx/ref/` remain active for defining the public API surface. - Check for platform-specific differences using file suffixes (`.netfx.cs`, `.netcore.cs`, `.windows.cs`, `.unix.cs`) and conditional compilation (`#if NETFRAMEWORK`, `#if NET`, `#if _WINDOWS`, `#if _UNIX`). +- Lines of code, comments, and other text should be a maximum of 100 characters (see `policy/coding-style.md`). - Respect API compatibility rules across .NET versions - Do not introduce breaking changes without proper justification and documentation - Use the `doc/` directory for any new documentation or updates to existing documentation diff --git a/eng/pipelines/common/templates/steps/override-sni-version.yml b/eng/pipelines/common/templates/steps/override-sni-version.yml index 3b275262c3..ebeac6f39c 100644 --- a/eng/pipelines/common/templates/steps/override-sni-version.yml +++ b/eng/pipelines/common/templates/steps/override-sni-version.yml @@ -15,13 +15,14 @@ steps: - task: PowerShell@2 displayName: Add SNI Validation Feed in Nuget.config inputs: + pwsh: true targetType: inline script: | Write-Host "SNI validation feed to use = ${{parameters.SNIValidationFeed}}" # define file to update $NugetCfg = Join-Path -Path '.' -ChildPath 'NuGet.config' - type $NugetCfg + Get-Content $NugetCfg # load content of xml from file defined above $xml = New-Object XML @@ -44,10 +45,11 @@ steps: # save the xml file $xml.Save($NugetCfg) - type $NugetCfg + Get-Content $NugetCfg - task: PowerShell@2 displayName: Update SNI Version in Versions.props inputs: + pwsh: true targetType: inline # TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/42204): # Package dependency versions have moved to Directory.Packages.props, so the below script no @@ -57,7 +59,7 @@ steps: # define file to update $PropsPath = Join-Path -Path '.' -ChildPath 'tools\props\Versions.props' - type $PropsPath + Get-Content $PropsPath # load content of xml from file defined above $xml = New-Object XML @@ -79,6 +81,6 @@ steps: # save the xml file $xml.Save($PropsPath) - type $PropsPath + Get-Content $PropsPath - task: NuGetAuthenticate@1 displayName: 'NuGet Authenticate with SNI Validation Feed' diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index afa5aa5918..748601876e 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -36,15 +36,19 @@ parameters: - name: isOfficial type: boolean + # True if this build is a preview. + - name: isPreview + type: boolean + jobs: - - job: validate_nuget_package - displayName: "Validate NuGet package" + - job: validate_signed_package + displayName: Verify SqlClient NuGet Package pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs isCustom: true name: ADO-1ES-Pool - vmImage: "ADO-MMS22-SQL19" + vmImage: ADO-Win25 variables: # More settings at https://aka.ms/obpipelines/yaml/jobs diff --git a/eng/pipelines/onebranch/jobs/validate-symbols-job.yml b/eng/pipelines/onebranch/jobs/validate-symbols-job.yml new file mode 100644 index 0000000000..96181040d8 --- /dev/null +++ b/eng/pipelines/onebranch/jobs/validate-symbols-job.yml @@ -0,0 +1,74 @@ +################################################################################# +# 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. # +################################################################################# + +# Validates that symbols (PDBs) for every published package DLL are available on each configured +# symbol server using the symchk tool. +# +# All packages specified in the 'packages' parameter will be checked against all symbol servers +# specified in the 'symbolServers' parameter. + +parameters: + # True to enable debug information and steps. + - name: debug + type: boolean + + # The list of symbol servers to verify against. Each entry has a 'name' (friendly display name) + # and 'url' (the symbol server URL that symchk can query). + - name: symbolServers + type: object + default: + - name: MSDL (Public) + url: https://msdl.microsoft.com/download/symbols + - name: SymWeb (Internal) + url: https://symweb.azurefd.net + + # The packages whose symbols should be verified. Each entry has: + # artifactName — pipeline artifact name (from onebranch-variables.yml) + # packageName — NuGet package name prefix used to locate the .nupkg file + # dllPath — relative path to the DLL inside the extracted package + - name: packages + type: object + default: [] + +jobs: + - job: validate_symbols + displayName: Verify symbols on symbol servers + + pool: + type: windows + isCustom: true + name: ADO-1ES-Pool + vmImage: ADO-Win25 + + variables: + extractRoot: $(Build.SourcesDirectory)\symchk_packages + + steps: + - ${{ if parameters.debug }}: + - script: SET + displayName: Print Environment Variables + + # ── Download, extract, and verify symbols for each package ────────────── + + - ${{ each pkg in parameters.packages }}: + - download: current + artifact: ${{ pkg.artifactName }} + patterns: '**/*.nupkg' + displayName: Download ${{ pkg.packageName }} + + - ${{ each server in parameters.symbolServers }}: + - task: PowerShell@2 + displayName: Verify ${{ pkg.packageName }} on ${{ server.name }} + inputs: + pwsh: true + filePath: eng/pipelines/onebranch/jobs/validate-symbols.ps1 + arguments: > + -ArtifactPath "$(Pipeline.Workspace)\${{ pkg.artifactName }}" + -ExtractPath "$(extractRoot)\${{ pkg.packageName }}" + -PackageName "${{ pkg.packageName }}" + -DllPath "${{ pkg.dllPath }}" + -SymbolServerUrl "${{ server.url }}" + -SymbolServerName "${{ server.name }}" diff --git a/eng/pipelines/onebranch/jobs/validate-symbols.ps1 b/eng/pipelines/onebranch/jobs/validate-symbols.ps1 new file mode 100644 index 0000000000..a34722017a --- /dev/null +++ b/eng/pipelines/onebranch/jobs/validate-symbols.ps1 @@ -0,0 +1,171 @@ +<# +.SYNOPSIS + Verifies that symbols (PDBs) for a single DLL are available on a symbol server. + +.DESCRIPTION + This script is called by the validate-symbols-job.yml Azure Pipelines job, + once per package per symbol server. It: + 1. Locates the .nupkg file in the downloaded artifact directory. + 2. Extracts the package contents (skipped if already extracted). + 3. Runs symchk.exe to verify that matching PDBs are available on the + specified symbol server. + + The script exits with a non-zero exit code if verification fails. + +.PARAMETER ArtifactPath + The directory containing the downloaded pipeline artifact (.nupkg files). + +.PARAMETER ExtractPath + The directory where the package will be extracted. + +.PARAMETER PackageName + The NuGet package name prefix used to locate the .nupkg file + (e.g. "Microsoft.Data.SqlClient"). + +.PARAMETER DllPath + The relative path to the DLL inside the extracted package + (e.g. "lib\net8.0\Microsoft.Data.SqlClient.dll"). + +.PARAMETER SymbolServerUrl + The symbol server URL that symchk can query + (e.g. "https://msdl.microsoft.com/download/symbols"). + +.PARAMETER SymbolServerName + A friendly display name for the symbol server, used in log output. + +.PARAMETER MaxRetries + Maximum number of attempts when symbols are not yet available. The + first attempt runs immediately; subsequent attempts wait + RetryIntervalSeconds between them. Defaults to 10 (~5 minutes total + with default interval). + +.PARAMETER RetryIntervalSeconds + Seconds to wait between retry attempts (default 30). + +.EXAMPLE + .\validate-symbols.ps1 ` + -ArtifactPath "C:\agent\_work\1\drop_SqlClient" ` + -ExtractPath "C:\agent\_work\1\s\symchk_packages\Microsoft.Data.SqlClient" ` + -PackageName "Microsoft.Data.SqlClient" ` + -DllPath "lib\net8.0\Microsoft.Data.SqlClient.dll" ` + -SymbolServerUrl "https://msdl.microsoft.com/download/symbols" ` + -SymbolServerName "MSDL (Public)" +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$ArtifactPath, + + [Parameter(Mandatory)] + [string]$ExtractPath, + + [Parameter(Mandatory)] + [string]$PackageName, + + [Parameter(Mandatory)] + [string]$DllPath, + + [Parameter(Mandatory)] + [string]$SymbolServerUrl, + + [Parameter(Mandatory)] + [string]$SymbolServerName, + + [int]$MaxRetries = 10, + + [int]$RetryIntervalSeconds = 30 +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# -- Extract the package (skip if already done) -------------------------------- + +$dllFullPath = Join-Path $ExtractPath $DllPath + +if (-not (Test-Path $dllFullPath)) { + Write-Host "Extracting $PackageName" + + New-Item -ItemType Directory -Force -Path $ExtractPath | Out-Null + + $nupkg = Get-ChildItem -Path $ArtifactPath -Filter "$PackageName.*.nupkg" ` + | Where-Object { $_.Name -notlike '*.snupkg' } ` + | Select-Object -First 1 + + if (-not $nupkg) { + Write-Host "##vso[task.logissue type=error]No $PackageName nupkg found in $ArtifactPath" + exit 1 + } + + Write-Host "Found: $($nupkg.FullName)" + + $zipPath = Join-Path $ExtractPath 'package.zip' + Copy-Item $nupkg.FullName $zipPath + Expand-Archive -Path $zipPath -DestinationPath $ExtractPath -Force + Remove-Item $zipPath +} + +if (-not (Test-Path $dllFullPath)) { + Write-Host "##vso[task.logissue type=error]DLL not found after extraction: $dllFullPath" + exit 1 +} + +# -- Locate symchk.exe --------------------------------------------------------- + +$symchkCandidates = @( + "${env:ProgramFiles(x86)}\Windows Kits\10\Debuggers\x64\symchk.exe" + "${env:ProgramFiles}\Windows Kits\10\Debuggers\x64\symchk.exe" +) + +$symchkPath = $null +foreach ($candidate in $symchkCandidates) { + if (Test-Path $candidate) { + $symchkPath = $candidate + break + } +} + +if (-not $symchkPath) { + Write-Host "##vso[task.logissue type=error]symchk.exe not found. Ensure Debugging Tools for Windows are installed." + exit 1 +} + +# -- Verify symbols (with retries for publishing latency) ---------------------- + +$dllLeaf = Split-Path $dllFullPath -Leaf + +Write-Host "Verifying symbols for $dllLeaf on $SymbolServerName ($SymbolServerUrl)" +Write-Host "Using symchk: $symchkPath" +Write-Host "Max attempts: $MaxRetries, interval: ${RetryIntervalSeconds}s" + +$symchkArgs = @( + $dllFullPath, + "/s", "srv*$SymbolServerUrl", + "/os" +) + +for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { + Write-Host "Attempt $attempt of $MaxRetries -- running: symchk $($symchkArgs -join ' ')" + $output = & $symchkPath @symchkArgs 2>&1 | Out-String + $symchkExit = $LASTEXITCODE + + Write-Host $output + + $passed = ($symchkExit -eq 0) -and + ($output -match "FAILED files = 0") -and + ($output -match "PASSED \+ IGNORED files = [1-9]") + + if ($passed) { + Write-Host "Symbols verified successfully for $dllLeaf on $SymbolServerName" + exit 0 + } + + if ($attempt -lt $MaxRetries) { + Write-Host "Symbols not yet available. Retrying in $RetryIntervalSeconds seconds..." + Start-Sleep -Seconds $RetryIntervalSeconds + } +} + +Write-Host "##vso[task.logissue type=error]symchk could not verify symbols for $dllLeaf on $SymbolServerName after $MaxRetries attempts." +exit 1 diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 4ae301020f..3a16b1b2da 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -231,13 +231,22 @@ extends: symbolsPublishTokenUri: '$(SymbolsPublishTokenUriPPE)' symbolsUploadAccount: '$(SymbolsUploadAccount)' - - template: /eng/pipelines/onebranch/stages/release-stages.yml@self + - template: /eng/pipelines/onebranch/stages/validation-stage.yml@self parameters: debug: ${{ parameters.debug }} + isOfficial: false + isPreview: ${{ parameters.isPreview }} + publishSymbols: ${{ parameters.publishSymbols }} + buildSqlServerServer: ${{ parameters.buildSqlServerServer }} + buildSqlClient: ${{ parameters.buildSqlClient }} + buildAKVProvider: ${{ parameters.buildAKVProvider }} + + - template: /eng/pipelines/onebranch/stages/release-stage.yml@self + parameters: + debug: ${{ parameters.debug }} + isOfficial: false # Non-official pipelines always push to the NuGet Test feed. releaseToProduction: false - # This is _not_ an official pipeline. - isOfficial: false stageNameSuffix: test publishSymbols: ${{ parameters.publishSymbols }} diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 96fb0dc5f5..4e9c0acab6 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -265,12 +265,21 @@ extends: symbolsPublishTokenUri: '$(SymbolsPublishTokenUriPPE)' symbolsUploadAccount: '$(SymbolsUploadAccount)' - - template: /eng/pipelines/onebranch/stages/release-stages.yml@self + - template: /eng/pipelines/onebranch/stages/validation-stage.yml@self parameters: debug: ${{ parameters.debug }} - releaseToProduction: ${{ parameters.releaseToProduction }} - # This is an official pipeline. isOfficial: true + isPreview: ${{ parameters.isPreview }} + publishSymbols: ${{ parameters.publishSymbols }} + buildSqlServerServer: ${{ parameters.buildSqlServerServer }} + buildSqlClient: ${{ parameters.buildSqlClient }} + buildAKVProvider: ${{ parameters.buildAKVProvider }} + + - template: /eng/pipelines/onebranch/stages/release-stage.yml@self + parameters: + debug: ${{ parameters.debug }} + isOfficial: true + releaseToProduction: ${{ parameters.releaseToProduction }} stageNameSuffix: production publishSymbols: ${{ parameters.publishSymbols }} diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 096da7e949..1b80221abc 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -4,15 +4,27 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# Unified build stages template for sqlclient OneBranch pipelines. -# Consumed by both the official and non-official pipeline definitions via: +# Unified build stages template for sqlclient OneBranch pipelines. Consumed by both the official +# and non-official pipeline definitions. # -# Depending on the build* parameters, this template will define the following stages: +# Stages defined by this template and their controlling parameters: # -# - build_independent: builds packages with no cross-package dependencies (Logging, SqlServer) -# - build_abstractions: builds the Abstractions package, which depends on Logging -# - build_dependent: builds packages with dependencies on Abstractions (SqlClient, Azure) -# - build_addons: builds add-on packages with dependencies on the core packages (Akv Provider) +# Stage | Condition | Packages +# -----------------------|------------------------------------------|----------------------------------- +# build_independent | Always (at least one job when any | Logging (buildSqlClient || buildAKVProvider) +# | build* param is true) | SqlServer (buildSqlServerServer) +# build_abstractions | buildSqlClient | Abstractions +# build_dependent | buildSqlClient | SqlClient, Azure +# build_addons | buildAKVProvider && buildSqlClient | AKV Provider +# +# This template depends on the following runtime (i.e. macro expansion) variables being defined: +# +# - effectiveSqlServerVersion +# - effectiveLoggingVersion +# - effectiveAbstractionsVersion +# - effectiveSqlClientVersion +# - effectiveAzureVersion +# - effectiveAkvProviderVersion parameters: # ── General parameters ───────────────────────────────────────────────── @@ -288,18 +300,3 @@ stages: packageFullName: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' packageShortName: 'AkvProvider' packageVersion: '${{ parameters.akvProviderPackageVersion }}' - - # ==================================================================== - # Validation - # ==================================================================== - - ${{ if eq(parameters.buildSqlClient, true) }}: - - stage: sqlclient_package_validation - displayName: "SqlClient Package Validation" - dependsOn: build_dependent - jobs: - - template: /eng/pipelines/onebranch/jobs/validate-signed-package-job.yml@self - parameters: - artifactName: '${{ parameters.sqlClientArtifactsName }}' - expectedFileVersion: '${{ parameters.sqlClientFileVersion }}' - expectedPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - isOfficial: ${{ parameters.isOfficial }} diff --git a/eng/pipelines/onebranch/stages/release-stages.yml b/eng/pipelines/onebranch/stages/release-stage.yml similarity index 90% rename from eng/pipelines/onebranch/stages/release-stages.yml rename to eng/pipelines/onebranch/stages/release-stage.yml index 6ee3fbc88e..b84e5c5242 100644 --- a/eng/pipelines/onebranch/stages/release-stages.yml +++ b/eng/pipelines/onebranch/stages/release-stage.yml @@ -4,15 +4,17 @@ # See the LICENSE file in the project root for more information. # ################################################################################# # -# Unified release stages template for dotnet-sqlclient OneBranch pipelines. -# Consumed by both the official and non-official pipeline definitions via: +# Unified release stage template for dotnet-sqlclient OneBranch pipelines. Consumed by both the +# official and non-official pipeline definitions. # -# stages: -# - template: /eng/pipelines/onebranch/stages/release-stages.yml@self -# parameters: -# isOfficial: true # or false -# releaseToProduction: false # or true -# ... +# Stages defined by this template and their controlling parameters: +# +# Stage | Condition | Packages +# -----------------------|--------------------------------------------------|----------------------------- +# release_ | Any release* parameter is true | One job per enabled release* +# | (releaseSqlServerServer, releaseLogging, | parameter +# | releaseAbstractions, releaseSqlClient, | +# | releaseAzure, releaseAKVProvider) | # # The isOfficial flag controls release-stage behaviour: # - Official → Production environment, approval gate. @@ -31,6 +33,10 @@ parameters: - name: debug type: boolean + # True for a OneBranch Official pipeline, false for a Non-Official pipeline. + - name: isOfficial + type: boolean + # Controls where packages are pushed during the release stage. # # When true, packages are pushed to the NuGet Production feed. @@ -40,10 +46,6 @@ parameters: type: boolean default: false - # True for a OneBranch Official pipeline, false for a Non-Official pipeline. - - name: isOfficial - type: boolean - # The stage name suffix, used in the stage ID (e.g. release_production) and display name. - name: stageNameSuffix type: string @@ -138,6 +140,11 @@ stages: ${{ else }}: displayName: Release to NuGet Test dependsOn: + # We always depend on validation. That stage has its own checks to gate execution of the + # validation jobs, so it's safe to depend on it unconditionally. + - validation + # Additionally, we depend on the build stages corresponding to any packages we intend to + # release. - ${{ if or(parameters.releaseSqlServerServer, parameters.releaseLogging) }}: - build_independent - ${{ if parameters.releaseAbstractions }}: diff --git a/eng/pipelines/onebranch/stages/validation-stage.yml b/eng/pipelines/onebranch/stages/validation-stage.yml new file mode 100644 index 0000000000..4ba1d467cd --- /dev/null +++ b/eng/pipelines/onebranch/stages/validation-stage.yml @@ -0,0 +1,125 @@ +################################################################################# +# 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. # +################################################################################# + +# Validation stage template. Runs after build stages complete and verifies the signed SqlClient +# package as well as (optionally) symbol availability on public and internal symbol servers. +# +# This template defines the validation stage. +# +# The validate-symbols-job checks the following packages depending on parameters: +# - SqlServer - when buildSqlServerServer is true. +# - Logging, Abstractions, SqlClient, Azure - when buildSqlClient is true. +# - AKV Provider - when buildSqlClient and buildAKVProvider are true. + +parameters: + # ── General parameters ───────────────────────────────────────────────── + + # True to enable debug information and steps. + - name: debug + type: boolean + + # True for a OneBranch Official pipeline, false for a Non-Official pipeline. + - name: isOfficial + type: boolean + + # True if this is a preview build. + - name: isPreview + type: boolean + + # True to verify that symbols are available on symbol servers. + - name: publishSymbols + type: boolean + + # ── Build parameters (control which packages appear in symbol checks) ── + + - name: buildSqlServerServer + type: boolean + + - name: buildSqlClient + type: boolean + + - name: buildAKVProvider + type: boolean + +stages: + - stage: validation + displayName: Validation + dependsOn: + - build_independent + - ${{ if eq(parameters.buildSqlClient, true) }}: + - build_abstractions + - build_dependent + - ${{ if eq(parameters.buildAKVProvider, true) }}: + - build_addons + + jobs: + # ── Signed-package validation ────────────────────────────────── + + # We currently only validate the SqlClient package. + - ${{ if eq(parameters.buildSqlClient, true) }}: + - template: /eng/pipelines/onebranch/jobs/validate-signed-package-job.yml@self + parameters: + artifactName: $(sqlClientArtifactsName) + isOfficial: ${{ parameters.isOfficial }} + isPreview: ${{ parameters.isPreview }} + + # ── Symbol-server validation ─────────────────────────────────── + - ${{ if eq(parameters.publishSymbols, true) }}: + - template: /eng/pipelines/onebranch/jobs/validate-symbols-job.yml@self + parameters: + debug: ${{ parameters.debug }} + packages: + # SqlServer — only when buildSqlServerServer is true. + - ${{ if eq(parameters.buildSqlServerServer, true) }}: + - artifactName: $(sqlServerArtifactsName) + packageName: Microsoft.SqlServer.Server + dllPath: lib\netstandard2.0\Microsoft.SqlServer.Server.dll + + # Packages built when buildSqlClient is true — Logging, Abstractions, SqlClient, + # Azure. + - ${{ if eq(parameters.buildSqlClient, true) }}: + + # Logging + - artifactName: $(loggingArtifactsName) + packageName: Microsoft.Data.SqlClient.Internal.Logging + dllPath: lib\netstandard2.0\Microsoft.Data.SqlClient.Internal.Logging.dll + + # Abstractions + - artifactName: $(abstractionsArtifactsName) + packageName: Microsoft.Data.SqlClient.Extensions.Abstractions + dllPath: lib\netstandard2.0\Microsoft.Data.SqlClient.Extensions.Abstractions.dll + + # SqlClient — verify all runtime DLLs (not lib/ or ref/). + - artifactName: $(sqlClientArtifactsName) + packageName: Microsoft.Data.SqlClient + dllPath: runtimes\win\lib\net462\Microsoft.Data.SqlClient.dll + + - artifactName: $(sqlClientArtifactsName) + packageName: Microsoft.Data.SqlClient + dllPath: runtimes\win\lib\net8.0\Microsoft.Data.SqlClient.dll + + - artifactName: $(sqlClientArtifactsName) + packageName: Microsoft.Data.SqlClient + dllPath: runtimes\win\lib\net9.0\Microsoft.Data.SqlClient.dll + + - artifactName: $(sqlClientArtifactsName) + packageName: Microsoft.Data.SqlClient + dllPath: runtimes\unix\lib\net8.0\Microsoft.Data.SqlClient.dll + + - artifactName: $(sqlClientArtifactsName) + packageName: Microsoft.Data.SqlClient + dllPath: runtimes\unix\lib\net9.0\Microsoft.Data.SqlClient.dll + + # Azure + - artifactName: $(azureArtifactsName) + packageName: Microsoft.Data.SqlClient.Extensions.Azure + dllPath: lib\netstandard2.0\Microsoft.Data.SqlClient.Extensions.Azure.dll + + # AKV Provider — only when buildAKVProvider is true. + - ${{ if eq(parameters.buildAKVProvider, true) }}: + - artifactName: $(akvArtifactsName) + packageName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + dllPath: lib\netstandard2.0\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.dll diff --git a/eng/pipelines/onebranch/steps/publish-symbols-step.yml b/eng/pipelines/onebranch/steps/publish-symbols-step.yml index dc794f480d..f8d9dc1ca6 100644 --- a/eng/pipelines/onebranch/steps/publish-symbols-step.yml +++ b/eng/pipelines/onebranch/steps/publish-symbols-step.yml @@ -23,41 +23,41 @@ # reference the same uploaded artifact. parameters: - # Name of the symbols artifact that will be published - - name: artifactName - type: string + # Name of the symbols artifact that will be published + - name: artifactName + type: string - # Azure subscription where the publishing task will execute - - name: azureSubscription - type: string + # Azure subscription where the publishing task will execute + - name: azureSubscription + type: string - # Package name, typically the name of the nuget package being built - - name: packageName - type: string + # Package name, typically the name of the nuget package being built + - name: packageName + type: string - # Project that symbols will belong to (decided during symbols onboarding) - - name: publishProjectName - type: string + # Project that symbols will belong to (decided during symbols onboarding) + - name: publishProjectName + type: string - # Where symbols publishing service is hosted, will be prepended to trafficmanager.net - - name: publishServer - type: string + # Where symbols publishing service is hosted, will be prepended to trafficmanager.net + - name: publishServer + type: string - # Whether to publish the uploaded symbols to the internal symbols servers - - name: publishToInternal - type: boolean + # Whether to publish the uploaded symbols to the internal symbols servers + - name: publishToInternal + type: boolean - # Whether to publish the uploaded symbols to the public symbols servers - - name: publishToPublic - type: boolean + # Whether to publish the uploaded symbols to the public symbols servers + - name: publishToPublic + type: boolean - # URI to use for requesting a bearer-token for publishing the symbols - - name: publishTokenUri - type: string + # URI to use for requesting a bearer-token for publishing the symbols + - name: publishTokenUri + type: string - # Pattern to use to search for pdb symbols files to upload/publish - - name: searchPattern - type: string + # Pattern to use to search for pdb symbols files to upload/publish + - name: searchPattern + type: string # Root folder to search for PDB files. When called from a build job this is typically # $(BUILD_OUTPUT); when called from the dedicated symbols stage it points at the @@ -70,9 +70,9 @@ parameters: - name: uploadAccount type: string - # Version of the symbols to publish, typically the same as the NuGet package version - - name: version - type: string + # Version of the symbols to publish, typically the same as the NuGet package version + - name: version + type: string steps: # NOTE: ArtifactServices.Symbol.AccountName is set as a job-level variable in diff --git a/eng/pipelines/onebranch/tests/README.md b/eng/pipelines/onebranch/tests/README.md new file mode 100644 index 0000000000..bea6005f52 --- /dev/null +++ b/eng/pipelines/onebranch/tests/README.md @@ -0,0 +1,77 @@ +# OneBranch Pipeline Tests + +This directory contains [Pester](https://pester.dev/) tests for the +PowerShell scripts used by the OneBranch build and validation pipelines. + +## Prerequisites + +| Tool | Version | Install | +| ---- | ------- | ------- | +| PowerShell (pwsh) | 7.2+ | [Install PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) | +| Pester | 5.x | `Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck` | + +## Running tests + +From the repository root: + +```powershell +pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/tests/ -Output Detailed" +``` + +Or run a single test file: + +```powershell +pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 -Output Detailed" +``` + +## Writing tests + +### File naming + +Test files must follow Pester's naming convention: + +```text +.Tests.ps1 +``` + +For example, tests for `jobs/validate-symbols.ps1` live in +`tests/validate-symbols.Tests.ps1`. + +### Locating the script under test + +Since scripts live in sibling directories (`jobs/`, `steps/`, etc.), +reference them relative to `$PSScriptRoot`: + +```powershell +BeforeAll { + $Script:ScriptPath = Join-Path $PSScriptRoot '..' 'jobs' 'my-script.ps1' +} +``` + +### Testing scripts that use `exit` + +Pipeline scripts typically use `exit` for control flow, which terminates +the PowerShell host. To test exit codes, run the script as a **child +process** via `Start-Process`: + +```powershell +$proc = Start-Process -FilePath 'pwsh' ` + -ArgumentList @('-NoProfile', '-NonInteractive', '-File', $scriptPath, ) ` + -NoNewWindow -Wait -PassThru ` + -RedirectStandardOutput $stdoutFile ` + -RedirectStandardError $stderrFile + +$proc.ExitCode | Should -Be 0 +``` + +### Mocking external tools + +When a script calls an external tool (e.g. `symchk.exe`), create a +patched copy of the script that replaces the tool path with a mock `.ps1` +script. See `validate-symbols.Tests.ps1` for an example of this pattern. + +## Test inventory + +| Test file | Script under test | What it covers | +| --------- | ----------------- | -------------- | +| `validate-symbols.Tests.ps1` | `jobs/validate-symbols.ps1` | Syntax validation, package discovery/extraction, symchk detection, retry logic | diff --git a/eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 b/eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 new file mode 100644 index 0000000000..941e024815 --- /dev/null +++ b/eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 @@ -0,0 +1,448 @@ +################################################################################# +# 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. # +################################################################################# + +# Pester tests for validate-symbols.ps1 +# +# Run with: pwsh -c "Invoke-Pester ./validate-symbols.Tests.ps1 -Output Detailed" + +BeforeAll { + $Script:ScriptPath = Join-Path $PSScriptRoot '..' 'jobs' 'validate-symbols.ps1' + + # Common parameters reused across tests. + $Script:CommonParams = @{ + SymbolServerUrl = 'https://example.com/symbols' + SymbolServerName = 'TestServer' + MaxRetries = 1 + RetryIntervalSeconds = 0 + } + + function Invoke-ValidateSymbols { + <# + .SYNOPSIS + Runs validate-symbols.ps1 as a child process and captures output + and exit code. + #> + param([hashtable]$Params) + + $argList = @('-NoProfile', '-NonInteractive', '-File', $Script:ScriptPath) + foreach ($kv in $Params.GetEnumerator()) { + $argList += "-$($kv.Key)" + $argList += $kv.Value + } + + $proc = Start-Process -FilePath 'pwsh' ` + -ArgumentList $argList ` + -NoNewWindow ` + -Wait ` + -PassThru ` + -RedirectStandardOutput "$TestDrive/stdout.txt" ` + -RedirectStandardError "$TestDrive/stderr.txt" + + return @{ + ExitCode = $proc.ExitCode + StdOut = (Get-Content "$TestDrive/stdout.txt" -Raw -ErrorAction SilentlyContinue) ?? '' + StdErr = (Get-Content "$TestDrive/stderr.txt" -Raw -ErrorAction SilentlyContinue) ?? '' + } + } + + function New-FakeNupkg { + <# + .SYNOPSIS + Creates a minimal .nupkg (zip) that contains a placeholder DLL + at the specified relative path. + #> + param( + [string]$OutputDir, + [string]$PackageName, + [string]$Version, + [string]$DllRelativePath + ) + + $stagingDir = Join-Path $TestDrive 'nupkg_staging' + if (Test-Path $stagingDir) { Remove-Item $stagingDir -Recurse -Force } + New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null + + # Create the DLL placeholder inside the staging directory. + $dllStaging = Join-Path $stagingDir $DllRelativePath + New-Item -ItemType Directory -Force -Path (Split-Path $dllStaging) | Out-Null + Set-Content -Path $dllStaging -Value 'FAKE_DLL' + + # Create the zip and rename to .nupkg. + $zipDest = Join-Path $OutputDir "$PackageName.$Version.nupkg" + Compress-Archive -Path "$stagingDir/*" -DestinationPath $zipDest -Force + + Remove-Item $stagingDir -Recurse -Force + return $zipDest + } +} + +# ============================================================================= +# 1. Script syntax / parse validation +# ============================================================================= + +Describe 'Script syntax' { + It 'parses without errors' { + $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseFile( + $Script:ScriptPath, + [ref]$null, + [ref]$errors + ) + $errors | Should -BeNullOrEmpty + } + + It 'contains no non-ASCII characters' { + $bytes = [System.IO.File]::ReadAllBytes($Script:ScriptPath) + $nonAscii = $bytes | Where-Object { $_ -gt 127 } + $nonAscii | Should -BeNullOrEmpty + } +} + +# ============================================================================= +# 2. Package discovery and extraction +# ============================================================================= + +Describe 'Package discovery' { + It 'exits 1 when no nupkg is found' { + $artifactDir = Join-Path $TestDrive 'empty_artifacts' + $extractDir = Join-Path $TestDrive 'extract_empty' + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'NoSuchPackage' + DllPath = 'lib/net8.0/NoSuchPackage.dll' + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'No NoSuchPackage nupkg found' + } + + It 'ignores .snupkg files when searching for the package' { + $artifactDir = Join-Path $TestDrive 'snupkg_artifacts' + $extractDir = Join-Path $TestDrive 'extract_snupkg' + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + # Create only a .snupkg — the script should not match it. + Set-Content -Path (Join-Path $artifactDir 'Pkg.1.0.0.snupkg') -Value 'SNUPKG' + + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'Pkg' + DllPath = 'lib/net8.0/Pkg.dll' + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'No Pkg nupkg found' + } + + It 'extracts the nupkg and finds the DLL' { + $artifactDir = Join-Path $TestDrive 'good_artifacts' + $extractDir = Join-Path $TestDrive 'extract_good' + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + $dllRel = Join-Path 'lib' 'net8.0' 'MyPackage.dll' + New-FakeNupkg -OutputDir $artifactDir ` + -PackageName 'MyPackage' ` + -Version '1.0.0' ` + -DllRelativePath $dllRel + + # The script will extract successfully, then fail looking for + # symchk (which is expected on non-Windows). + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'MyPackage' + DllPath = $dllRel + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + # The DLL should have been extracted. + $extractedDll = Join-Path $extractDir $dllRel + $extractedDll | Should -Exist + + # On non-Windows, expect symchk-not-found exit. + # On Windows without Debugging Tools, same result. + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'Found:.*MyPackage\.1\.0\.0\.nupkg' + } + + It 'skips extraction when DLL already exists' { + $artifactDir = Join-Path $TestDrive 'pre_extracted_artifacts' + $extractDir = Join-Path $TestDrive 'pre_extracted' + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + $dllRel = Join-Path 'lib' 'net8.0' 'Already.dll' + + # Pre-create the DLL so extraction is skipped. + $dllFull = Join-Path $extractDir $dllRel + New-Item -ItemType Directory -Force -Path (Split-Path $dllFull) | Out-Null + Set-Content -Path $dllFull -Value 'PRE_EXISTING' + + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'Already' + DllPath = $dllRel + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + # Should NOT see "Extracting" in output since DLL already existed. + $result.StdOut | Should -Not -Match 'Extracting Already' + } + + It 'exits 1 when DLL is missing after extraction' { + $artifactDir = Join-Path $TestDrive 'wrong_dll_artifacts' + $extractDir = Join-Path $TestDrive 'extract_wrong_dll' + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + # Create nupkg with DLL at one path but ask for a different path. + $actualDll = Join-Path 'lib' 'net8.0' 'Actual.dll' + New-FakeNupkg -OutputDir $artifactDir ` + -PackageName 'MissingDll' ` + -Version '2.0.0' ` + -DllRelativePath $actualDll + + $requestedDll = Join-Path 'lib' 'net9.0' 'MissingDll.dll' + + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'MissingDll' + DllPath = $requestedDll + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'DLL not found after extraction' + } +} + +# ============================================================================= +# 3. symchk detection +# ============================================================================= + +Describe 'symchk detection' { + It 'exits 1 when symchk.exe is not found' { + $artifactDir = Join-Path $TestDrive 'symchk_artifacts' + $extractDir = Join-Path $TestDrive 'symchk_extract' + New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + + $dllRel = Join-Path 'lib' 'net8.0' 'Test.dll' + + # Pre-create DLL so extraction is skipped and we go straight to + # symchk detection. + $dllFull = Join-Path $extractDir $dllRel + New-Item -ItemType Directory -Force -Path (Split-Path $dllFull) | Out-Null + Set-Content -Path $dllFull -Value 'FAKE' + + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'Test' + DllPath = $dllRel + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + # Unless running on a Windows box with Debugging Tools installed, + # symchk.exe won't be found. + if (-not ($IsWindows -and ( + (Test-Path "${env:ProgramFiles(x86)}\Windows Kits\10\Debuggers\x64\symchk.exe") -or + (Test-Path "$env:ProgramFiles\Windows Kits\10\Debuggers\x64\symchk.exe") + ))) { + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'symchk\.exe not found' + } + } +} + +# ============================================================================= +# 4. Symbol verification (retry logic) +# +# These tests create a mock symchk script to simulate pass/fail output. +# A patched copy of validate-symbols.ps1 is generated that points to the +# mock instead of the real Windows SDK symchk.exe. +# ============================================================================= + +Describe 'Symbol verification with mock symchk' { + BeforeAll { + $Script:MockDir = Join-Path $TestDrive 'mock_symchk' + New-Item -ItemType Directory -Force -Path $Script:MockDir | Out-Null + + $Script:MockSymchk = Join-Path $Script:MockDir 'symchk.ps1' + $Script:ControlFile = Join-Path $Script:MockDir 'control.txt' + $Script:PatchedScript = Join-Path $TestDrive 'validate-symbols-patched.ps1' + + # Mock symchk: reads one exit-code line from control.txt per + # invocation, shifts remaining lines for retry support. + Set-Content -Path $Script:MockSymchk -Value @' +# Mock symchk — positional args: /s /os +$controlFile = Join-Path $PSScriptRoot 'control.txt' +$lines = @(Get-Content $controlFile -ErrorAction Stop) +$code = [int]$lines[0] +if ($lines.Count -gt 1) { + Set-Content $controlFile ($lines[1..($lines.Count - 1)]) +} else { + Set-Content $controlFile '' +} +$dll = Split-Path $args[0] -Leaf +if ($code -eq 0) { + Write-Output "SYMCHK: $dll PASSED" + Write-Output "SYMCHK: PASSED files = 1" + Write-Output "SYMCHK: FAILED files = 0" + Write-Output "SYMCHK: PASSED + IGNORED files = 1" +} else { + Write-Output "SYMCHK: $dll FAILED" + Write-Output "SYMCHK: PASSED files = 0" + Write-Output "SYMCHK: FAILED files = 1" + Write-Output "SYMCHK: PASSED + IGNORED files = 0" +} +exit $code +'@ + + # Build the patched script: replace symchk candidate list and + # symchk invocation so that our .ps1 mock is used. + $lines = Get-Content $Script:ScriptPath + + # Find and replace the $symchkCandidates block (spans multiple lines). + $inBlock = $false + $patchedLines = @() + foreach ($line in $lines) { + if ($line -match '^\$symchkCandidates\s*=\s*@\(') { + $inBlock = $true + $patchedLines += "`$symchkCandidates = @(`"$($Script:MockSymchk)`")" + continue + } + if ($inBlock) { + # Skip lines until the closing paren of the array. + if ($line -match '^\)') { $inBlock = $false } + continue + } + # Replace the symchk invocation to call our .ps1 mock via pwsh. + if ($line -match '\& \$symchkPath @symchkArgs') { + $line = $line -replace ` + '\& \$symchkPath @symchkArgs', ` + '& pwsh -NoProfile -File $symchkPath @symchkArgs' + } + $patchedLines += $line + } + + Set-Content -Path $Script:PatchedScript -Value $patchedLines + + # Helper function for running the patched script as a subprocess. + function Script:Invoke-PatchedScript { + param( + [string[]]$ControlSequence, + [int]$MaxRetries = 1 + ) + + Set-Content -Path $Script:ControlFile ` + -Value ($ControlSequence -join "`n") + + $guid = [guid]::NewGuid().ToString('N') + $stdoutFile = Join-Path $TestDrive "out_$guid.txt" + $stderrFile = Join-Path $TestDrive "err_$guid.txt" + + $proc = Start-Process -FilePath 'pwsh' ` + -ArgumentList @( + '-NoProfile', '-NonInteractive', + '-File', $Script:PatchedScript, + '-ArtifactPath', $Script:MockArtifactDir, + '-ExtractPath', $Script:MockExtractDir, + '-PackageName', 'Mock', + '-DllPath', $Script:MockDllRel, + '-SymbolServerUrl', 'https://example.com/symbols', + '-SymbolServerName', 'TestServer', + '-MaxRetries', $MaxRetries, + '-RetryIntervalSeconds', 0 + ) ` + -NoNewWindow -Wait -PassThru ` + -RedirectStandardOutput $stdoutFile ` + -RedirectStandardError $stderrFile + + return @{ + ExitCode = $proc.ExitCode + StdOut = (Get-Content $stdoutFile -Raw ` + -ErrorAction SilentlyContinue) ?? '' + StdErr = (Get-Content $stderrFile -Raw ` + -ErrorAction SilentlyContinue) ?? '' + } + } + } + + BeforeEach { + # Fresh directories for each test. + $guid = [guid]::NewGuid().ToString('N') + $Script:MockExtractDir = Join-Path $TestDrive "extract_$guid" + $Script:MockArtifactDir = Join-Path $TestDrive "art_$guid" + New-Item -ItemType Directory -Force -Path $Script:MockArtifactDir | Out-Null + + $Script:MockDllRel = Join-Path 'lib' 'net8.0' 'Mock.dll' + $dllFull = Join-Path $Script:MockExtractDir $Script:MockDllRel + New-Item -ItemType Directory -Force -Path (Split-Path $dllFull) | Out-Null + Set-Content -Path $dllFull -Value 'MOCK_DLL' + } + + It 'patched script parses without errors' { + $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseFile( + $Script:PatchedScript, [ref]$null, [ref]$errors + ) + $errors | Should -BeNullOrEmpty + } + + It 'exits 0 when symchk passes on first attempt' { + $result = Invoke-PatchedScript -ControlSequence @('0') + + $result.ExitCode | Should -Be 0 + $result.StdOut | Should -Match 'Symbols verified successfully' + } + + It 'exits 1 when symchk fails on all attempts' { + $result = Invoke-PatchedScript -ControlSequence @('1') -MaxRetries 1 + + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'could not verify symbols' + } + + It 'retries and succeeds when symchk fails then passes' { + $result = Invoke-PatchedScript -ControlSequence @('1', '0') -MaxRetries 2 + + $result.ExitCode | Should -Be 0 + $result.StdOut | Should -Match 'Attempt 1 of 2' + $result.StdOut | Should -Match 'Symbols verified successfully' + } + + It 'exhausts all retries when symchk keeps failing' { + $result = Invoke-PatchedScript -ControlSequence @('1', '1', '1') -MaxRetries 3 + + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'Attempt 3 of 3' + $result.StdOut | Should -Match 'could not verify symbols.*after 3 attempts' + } +} From 320ea2caf866472ee4f0ff9814bcdbd747cf9d9c Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:57:49 -0300 Subject: [PATCH 2/3] - Updated stage documentation to be complete and accurate. - Moved validate symbols script into scripts/ . --- .../onebranch/jobs/validate-symbols-job.yml | 2 +- .../onebranch/scripts/tests/README.md | 78 ++++++++++++++----- .../tests/validate-symbols.Tests.ps1 | 2 +- .../{jobs => scripts}/validate-symbols.ps1 | 0 .../onebranch/stages/build-stages.yml | 25 +++--- .../stages/publish-symbols-stage.yml | 10 ++- .../onebranch/stages/release-stage.yml | 16 ++-- .../onebranch/stages/validation-stage.yml | 20 +++-- eng/pipelines/onebranch/tests/README.md | 77 ------------------ 9 files changed, 102 insertions(+), 128 deletions(-) rename eng/pipelines/onebranch/{ => scripts}/tests/validate-symbols.Tests.ps1 (99%) rename eng/pipelines/onebranch/{jobs => scripts}/validate-symbols.ps1 (100%) delete mode 100644 eng/pipelines/onebranch/tests/README.md diff --git a/eng/pipelines/onebranch/jobs/validate-symbols-job.yml b/eng/pipelines/onebranch/jobs/validate-symbols-job.yml index 96181040d8..666b17375c 100644 --- a/eng/pipelines/onebranch/jobs/validate-symbols-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-symbols-job.yml @@ -64,7 +64,7 @@ jobs: displayName: Verify ${{ pkg.packageName }} on ${{ server.name }} inputs: pwsh: true - filePath: eng/pipelines/onebranch/jobs/validate-symbols.ps1 + filePath: eng/pipelines/onebranch/scripts/validate-symbols.ps1 arguments: > -ArtifactPath "$(Pipeline.Workspace)\${{ pkg.artifactName }}" -ExtractPath "$(extractRoot)\${{ pkg.packageName }}" diff --git a/eng/pipelines/onebranch/scripts/tests/README.md b/eng/pipelines/onebranch/scripts/tests/README.md index 447dda5d32..8603f6aec4 100644 --- a/eng/pipelines/onebranch/scripts/tests/README.md +++ b/eng/pipelines/onebranch/scripts/tests/README.md @@ -1,43 +1,79 @@ -# Publish-Symbols Tests +# OneBranch Script Tests -Pester tests for the `publish-symbols.ps1` script used by the symbol publishing pipeline step. +This directory contains [Pester](https://pester.dev/) tests for PowerShell +scripts used by the OneBranch pipelines. ## Prerequisites -- PowerShell 5.1+ or PowerShell 7+ -- [Pester v5](https://pester.dev/) (`Install-Module Pester -MinimumVersion 5.0 -Scope CurrentUser`) +| Tool | Version | Install | +| ---- | ------- | ------- | +| PowerShell (pwsh) | 7.2+ | [Install PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) | +| Pester | 5.x | `Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck` | -## Running the Tests +## Running tests -From this directory: +From the repository root: ```powershell -Invoke-Pester ./publish-symbols.Tests.ps1 +pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/scripts/tests/ -Output Detailed" ``` -Or from the repository root: +Run a single test file: ```powershell -Invoke-Pester ./eng/pipelines/onebranch/scripts/tests/ +pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/scripts/tests/publish-symbols.Tests.ps1 -Output Detailed" +pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 -Output Detailed" ``` -For detailed output: +## Writing tests + +### File naming + +Test files must follow Pester naming conventions: + +```text +.Tests.ps1 +``` + +### Locating the script under test + +When scripts and tests are siblings under `scripts/` and `scripts/tests/`, +reference scripts relative to `$PSScriptRoot`: + +```powershell +BeforeAll { + $Script:ScriptPath = Join-Path $PSScriptRoot '..' 'my-script.ps1' +} +``` + +### Testing scripts that use `exit` + +Pipeline scripts commonly use `exit` for control flow. To validate exit codes, +run scripts as child processes with `Start-Process`: ```powershell -Invoke-Pester ./publish-symbols.Tests.ps1 -Output Detailed +$proc = Start-Process -FilePath 'pwsh' ` + -ArgumentList @('-NoProfile', '-NonInteractive', '-File', $scriptPath, ) ` + -NoNewWindow -Wait -PassThru ` + -RedirectStandardOutput $stdoutFile ` + -RedirectStandardError $stderrFile + +$proc.ExitCode | Should -Be 0 ``` -## Test Coverage +### Mocking external tools + +When a script calls external tools (for example `symchk.exe`, `az`, or +`Invoke-RestMethod`), mock those calls in tests. See +`validate-symbols.Tests.ps1` and `publish-symbols.Tests.ps1`. + +## Test inventory -| Area | What's tested | -| --------------------- | ---------------------------------------------------------------- | -| Parameter validation | Empty strings rejected for all mandatory parameters | -| URL construction | Base URL, register URL, request URL built from parameters | -| Request bodies | Registration body, default publish flags, flag overrides | -| Error handling | Token failure, registration failure, publish failure, status failure — all verify expanded URI in error message | -| Status validation | Detects Failed/Cancelled results, respects PublishToInternal/PublishToPublic flags, passes on Succeeded/Pending | +| Test file | Script under test | What it covers | +| --------- | ----------------- | -------------- | +| `publish-symbols.Tests.ps1` | `scripts/publish-symbols.ps1` | Parameter validation, URL construction, request bodies, status validation, error handling | +| `validate-symbols.Tests.ps1` | `scripts/validate-symbols.ps1` | Syntax validation, package discovery/extraction, symchk detection, retry logic | ## Notes -- All external calls (`az`, `Invoke-RestMethod`) are mocked — no network access or Azure credentials are required. -- Tests validate the script at `../publish-symbols.ps1` relative to this directory. +- Tests for `publish-symbols.ps1` mock all external calls (`az`, `Invoke-RestMethod`), so no network access or Azure credentials are required. diff --git a/eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 b/eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 similarity index 99% rename from eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 rename to eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 index 941e024815..0b5f3be090 100644 --- a/eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 +++ b/eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 @@ -9,7 +9,7 @@ # Run with: pwsh -c "Invoke-Pester ./validate-symbols.Tests.ps1 -Output Detailed" BeforeAll { - $Script:ScriptPath = Join-Path $PSScriptRoot '..' 'jobs' 'validate-symbols.ps1' + $Script:ScriptPath = Join-Path $PSScriptRoot '..' 'validate-symbols.ps1' # Common parameters reused across tests. $Script:CommonParams = @{ diff --git a/eng/pipelines/onebranch/jobs/validate-symbols.ps1 b/eng/pipelines/onebranch/scripts/validate-symbols.ps1 similarity index 100% rename from eng/pipelines/onebranch/jobs/validate-symbols.ps1 rename to eng/pipelines/onebranch/scripts/validate-symbols.ps1 diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 1b80221abc..8a87d766b7 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -4,18 +4,25 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# Unified build stages template for sqlclient OneBranch pipelines. Consumed by both the official -# and non-official pipeline definitions. +# Unified build stages template for SqlClient OneBranch pipelines. Consumed by +# both the official and non-official pipeline definitions. # # Stages defined by this template and their controlling parameters: # -# Stage | Condition | Packages -# -----------------------|------------------------------------------|----------------------------------- -# build_independent | Always (at least one job when any | Logging (buildSqlClient || buildAKVProvider) -# | build* param is true) | SqlServer (buildSqlServerServer) -# build_abstractions | buildSqlClient | Abstractions -# build_dependent | buildSqlClient | SqlClient, Azure -# build_addons | buildAKVProvider && buildSqlClient | AKV Provider +# Stage | Condition | Produced packages +# -------------------|----------------------------------------|----------------------------------- +# build_independent | Always emitted | Logging (buildSqlClient || +# | | buildAkvProvider) +# | | SqlServer (buildSqlServerServer) +# build_abstractions | buildSqlClient | Abstractions +# build_dependent | buildSqlClient | SqlClient, Azure +# build_addons | buildAkvProvider && buildSqlClient | AKV Provider +# +# Notes: +# - build_independent is always present so downstream stages can depend on a +# stable stage name; individual jobs inside it are compile-time conditional. +# - build_dependent stage name is intentionally stable because downstream +# templates depend on it by name. # # This template depends on the following runtime (i.e. macro expansion) variables being defined: # diff --git a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml index 56e8f827b8..691424d69f 100644 --- a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml +++ b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml @@ -4,12 +4,14 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# Unified symbols publishing stage template for sqlclient OneBranch pipelines. +# Unified symbols publishing stage template for SqlClient OneBranch pipelines. # Consumed by both the official and non-official pipeline definitions. # -# This stage publishes PDB symbol files to the internal and public symbol servers. -# Each package's PDBs are published in a separate job to maintain unique naming and -# versioning information per package. +# Stage defined by this template: +# - publish_symbols (emitted only when publishSymbols is true) +# +# This stage publishes PDB symbol files for each built package in separate jobs +# to preserve package-specific naming and versioning metadata. # # PDBs are expected to be located under the 'symbols/' directory at the root of # each build artifact, with target framework subdirectories preserved by the build diff --git a/eng/pipelines/onebranch/stages/release-stage.yml b/eng/pipelines/onebranch/stages/release-stage.yml index b84e5c5242..65293c566e 100644 --- a/eng/pipelines/onebranch/stages/release-stage.yml +++ b/eng/pipelines/onebranch/stages/release-stage.yml @@ -4,17 +4,17 @@ # See the LICENSE file in the project root for more information. # ################################################################################# # -# Unified release stage template for dotnet-sqlclient OneBranch pipelines. Consumed by both the -# official and non-official pipeline definitions. +# Unified release stage template for dotnet-sqlclient OneBranch pipelines. +# Consumed by both the official and non-official pipeline definitions. # # Stages defined by this template and their controlling parameters: # -# Stage | Condition | Packages -# -----------------------|--------------------------------------------------|----------------------------- -# release_ | Any release* parameter is true | One job per enabled release* -# | (releaseSqlServerServer, releaseLogging, | parameter -# | releaseAbstractions, releaseSqlClient, | -# | releaseAzure, releaseAKVProvider) | +# Stage | Condition | Packages +# -----------------------|-------------------------------------------------|------------------------------ +# release_ | Any release* parameter is true | One publish job per enabled +# | (releaseSqlServerServer, releaseLogging, | release parameter +# | releaseAbstractions, releaseSqlClient, | +# | releaseAzure, releaseAkvProvider) | # # The isOfficial flag controls release-stage behaviour: # - Official → Production environment, approval gate. diff --git a/eng/pipelines/onebranch/stages/validation-stage.yml b/eng/pipelines/onebranch/stages/validation-stage.yml index 4ba1d467cd..e8ad9849de 100644 --- a/eng/pipelines/onebranch/stages/validation-stage.yml +++ b/eng/pipelines/onebranch/stages/validation-stage.yml @@ -4,15 +4,21 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# Validation stage template. Runs after build stages complete and verifies the signed SqlClient -# package as well as (optionally) symbol availability on public and internal symbol servers. +# Validation stage template. Runs after build stages complete and verifies the +# signed SqlClient package, and optionally validates symbol availability. # -# This template defines the validation stage. +# Stage defined by this template: +# - validation (always emitted) # -# The validate-symbols-job checks the following packages depending on parameters: -# - SqlServer - when buildSqlServerServer is true. -# - Logging, Abstractions, SqlClient, Azure - when buildSqlClient is true. -# - AKV Provider - when buildSqlClient and buildAKVProvider are true. +# Jobs included in the validation stage: +# - validate-signed-package-job when buildSqlClient is true +# - validate-symbols-job when publishSymbols is true +# +# The validate-symbols-job checks the following packages depending on +# parameters: +# - SqlServer when buildSqlServerServer is true. +# - Logging, Abstractions, SqlClient, Azure when buildSqlClient is true. +# - AKV Provider when buildSqlClient and buildAKVProvider are true. parameters: # ── General parameters ───────────────────────────────────────────────── diff --git a/eng/pipelines/onebranch/tests/README.md b/eng/pipelines/onebranch/tests/README.md deleted file mode 100644 index bea6005f52..0000000000 --- a/eng/pipelines/onebranch/tests/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# OneBranch Pipeline Tests - -This directory contains [Pester](https://pester.dev/) tests for the -PowerShell scripts used by the OneBranch build and validation pipelines. - -## Prerequisites - -| Tool | Version | Install | -| ---- | ------- | ------- | -| PowerShell (pwsh) | 7.2+ | [Install PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) | -| Pester | 5.x | `Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck` | - -## Running tests - -From the repository root: - -```powershell -pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/tests/ -Output Detailed" -``` - -Or run a single test file: - -```powershell -pwsh -c "Invoke-Pester ./eng/pipelines/onebranch/tests/validate-symbols.Tests.ps1 -Output Detailed" -``` - -## Writing tests - -### File naming - -Test files must follow Pester's naming convention: - -```text -.Tests.ps1 -``` - -For example, tests for `jobs/validate-symbols.ps1` live in -`tests/validate-symbols.Tests.ps1`. - -### Locating the script under test - -Since scripts live in sibling directories (`jobs/`, `steps/`, etc.), -reference them relative to `$PSScriptRoot`: - -```powershell -BeforeAll { - $Script:ScriptPath = Join-Path $PSScriptRoot '..' 'jobs' 'my-script.ps1' -} -``` - -### Testing scripts that use `exit` - -Pipeline scripts typically use `exit` for control flow, which terminates -the PowerShell host. To test exit codes, run the script as a **child -process** via `Start-Process`: - -```powershell -$proc = Start-Process -FilePath 'pwsh' ` - -ArgumentList @('-NoProfile', '-NonInteractive', '-File', $scriptPath, ) ` - -NoNewWindow -Wait -PassThru ` - -RedirectStandardOutput $stdoutFile ` - -RedirectStandardError $stderrFile - -$proc.ExitCode | Should -Be 0 -``` - -### Mocking external tools - -When a script calls an external tool (e.g. `symchk.exe`), create a -patched copy of the script that replaces the tool path with a mock `.ps1` -script. See `validate-symbols.Tests.ps1` for an example of this pattern. - -## Test inventory - -| Test file | Script under test | What it covers | -| --------- | ----------------- | -------------- | -| `validate-symbols.Tests.ps1` | `jobs/validate-symbols.ps1` | Syntax validation, package discovery/extraction, symchk detection, retry logic | From 9c9253329c3dbf4f49cdcef1f0f7922cd70a80e9 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:47:27 -0300 Subject: [PATCH 3/3] Address PR 4073 review feedback and pool docs --- .../onebranch/jobs/build-buildproj-job.yml | 3 ++ .../jobs/publish-nuget-package-job.yml | 3 ++ .../onebranch/jobs/publish-symbols-job.yml | 3 ++ .../jobs/validate-signed-package-job.yml | 11 ++++--- .../onebranch/jobs/validate-symbols-job.yml | 3 ++ .../scripts/tests/validate-symbols.Tests.ps1 | 29 +++++++++++++++++++ .../onebranch/scripts/validate-symbols.ps1 | 2 +- .../onebranch/sqlclient-non-official.yml | 4 +-- .../onebranch/sqlclient-official.yml | 4 +-- .../onebranch/stages/build-stages.yml | 9 ------ .../stages/publish-symbols-stage.yml | 3 ++ .../onebranch/stages/release-stage.yml | 4 --- .../onebranch/stages/validation-stage.yml | 9 +++--- .../onebranch/steps/publish-symbols-step.yml | 20 ++++++------- 14 files changed, 66 insertions(+), 41 deletions(-) diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml index 3fa44c3ce6..94bf86b881 100644 --- a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -92,6 +92,9 @@ parameters: jobs: - job: 'build_package_${{ parameters.packageShortName }}' displayName: 'Build: ${{ parameters.packageFullName }}' + # Pool schema docs (OneBranch): https://aka.ms/obpipelines/yaml/jobs + # Agent pool concepts (Azure Pipelines): + # https://learn.microsoft.com/azure/devops/pipelines/agents/pools-queues pool: type: windows diff --git a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml index 21d36b9640..c69b351ad6 100644 --- a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml @@ -86,6 +86,9 @@ jobs: artifactName: ${{ parameters.artifactName }} targetPath: $(artifactPath) + # Pool schema docs (OneBranch): https://aka.ms/obpipelines/yaml/jobs + # Agent pool concepts (Azure Pipelines): + # https://learn.microsoft.com/azure/devops/pipelines/agents/pools-queues pool: type: release diff --git a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml index 8ee37ad7d7..f89da382ad 100644 --- a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml @@ -49,6 +49,9 @@ parameters: jobs: - job: 'publish_symbols_${{ parameters.packageShortName }}' displayName: 'Publish Symbols: ${{ parameters.packageFullName }}' + # Pool schema docs (OneBranch): https://aka.ms/obpipelines/yaml/jobs + # Agent pool concepts (Azure Pipelines): + # https://learn.microsoft.com/azure/devops/pipelines/agents/pools-queues pool: type: linux diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index 748601876e..87681ea7b0 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -36,21 +36,20 @@ parameters: - name: isOfficial type: boolean - # True if this build is a preview. - - name: isPreview - type: boolean - jobs: - job: validate_signed_package displayName: Verify SqlClient NuGet Package + # Pool schema docs (OneBranch): https://aka.ms/obpipelines/yaml/jobs + # Agent pool concepts (Azure Pipelines): + # https://learn.microsoft.com/azure/devops/pipelines/agents/pools-queues pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + type: windows isCustom: true name: ADO-1ES-Pool vmImage: ADO-Win25 - variables: # More settings at https://aka.ms/obpipelines/yaml/jobs + variables: # Path within the downloaded artifact where NuGet packages are located. - name: artifactPath diff --git a/eng/pipelines/onebranch/jobs/validate-symbols-job.yml b/eng/pipelines/onebranch/jobs/validate-symbols-job.yml index 666b17375c..7e5627268d 100644 --- a/eng/pipelines/onebranch/jobs/validate-symbols-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-symbols-job.yml @@ -37,6 +37,9 @@ jobs: - job: validate_symbols displayName: Verify symbols on symbol servers + # Pool schema docs (OneBranch): https://aka.ms/obpipelines/yaml/jobs + # Agent pool concepts (Azure Pipelines): + # https://learn.microsoft.com/azure/devops/pipelines/agents/pools-queues pool: type: windows isCustom: true diff --git a/eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 b/eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 index 0b5f3be090..5c20341440 100644 --- a/eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 +++ b/eng/pipelines/onebranch/scripts/tests/validate-symbols.Tests.ps1 @@ -183,6 +183,35 @@ Describe 'Package discovery' { $result.StdOut | Should -Match 'Found:.*MyPackage\.1\.0\.0\.nupkg' } + It 'finds nupkg under packages subfolder' { + $artifactDir = Join-Path $TestDrive 'nested_artifacts' + $packageDir = Join-Path $artifactDir 'packages' + $extractDir = Join-Path $TestDrive 'extract_nested' + New-Item -ItemType Directory -Force -Path $packageDir | Out-Null + + $dllRel = Join-Path 'lib' 'net8.0' 'NestedPackage.dll' + New-FakeNupkg -OutputDir $packageDir ` + -PackageName 'NestedPackage' ` + -Version '1.2.3' ` + -DllRelativePath $dllRel + + $result = Invoke-ValidateSymbols @{ + ArtifactPath = $artifactDir + ExtractPath = $extractDir + PackageName = 'NestedPackage' + DllPath = $dllRel + SymbolServerUrl = $Script:CommonParams.SymbolServerUrl + SymbolServerName = $Script:CommonParams.SymbolServerName + MaxRetries = '1' + RetryIntervalSeconds = '0' + } + + $extractedDll = Join-Path $extractDir $dllRel + $extractedDll | Should -Exist + $result.ExitCode | Should -Be 1 + $result.StdOut | Should -Match 'Found:.*NestedPackage\.1\.2\.3\.nupkg' + } + It 'skips extraction when DLL already exists' { $artifactDir = Join-Path $TestDrive 'pre_extracted_artifacts' $extractDir = Join-Path $TestDrive 'pre_extracted' diff --git a/eng/pipelines/onebranch/scripts/validate-symbols.ps1 b/eng/pipelines/onebranch/scripts/validate-symbols.ps1 index a34722017a..9d95757b32 100644 --- a/eng/pipelines/onebranch/scripts/validate-symbols.ps1 +++ b/eng/pipelines/onebranch/scripts/validate-symbols.ps1 @@ -89,7 +89,7 @@ if (-not (Test-Path $dllFullPath)) { New-Item -ItemType Directory -Force -Path $ExtractPath | Out-Null - $nupkg = Get-ChildItem -Path $ArtifactPath -Filter "$PackageName.*.nupkg" ` + $nupkg = Get-ChildItem -Path $ArtifactPath -Recurse -File -Filter "$PackageName.*.nupkg" ` | Where-Object { $_.Name -notlike '*.snupkg' } ` | Select-Object -First 1 diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 3a16b1b2da..cb28729abc 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -235,15 +235,13 @@ extends: parameters: debug: ${{ parameters.debug }} isOfficial: false - isPreview: ${{ parameters.isPreview }} publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} - buildAKVProvider: ${{ parameters.buildAKVProvider }} + buildAKVProvider: ${{ parameters.buildAkvProvider }} - template: /eng/pipelines/onebranch/stages/release-stage.yml@self parameters: - debug: ${{ parameters.debug }} isOfficial: false # Non-official pipelines always push to the NuGet Test feed. releaseToProduction: false diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 4e9c0acab6..0f0d5f1fae 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -269,15 +269,13 @@ extends: parameters: debug: ${{ parameters.debug }} isOfficial: true - isPreview: ${{ parameters.isPreview }} publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} - buildAKVProvider: ${{ parameters.buildAKVProvider }} + buildAKVProvider: ${{ parameters.buildAkvProvider }} - template: /eng/pipelines/onebranch/stages/release-stage.yml@self parameters: - debug: ${{ parameters.debug }} isOfficial: true releaseToProduction: ${{ parameters.releaseToProduction }} stageNameSuffix: production diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 8a87d766b7..1ccab38f42 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -24,15 +24,6 @@ # - build_dependent stage name is intentionally stable because downstream # templates depend on it by name. # -# This template depends on the following runtime (i.e. macro expansion) variables being defined: -# -# - effectiveSqlServerVersion -# - effectiveLoggingVersion -# - effectiveAbstractionsVersion -# - effectiveSqlClientVersion -# - effectiveAzureVersion -# - effectiveAkvProviderVersion - parameters: # ── General parameters ───────────────────────────────────────────────── diff --git a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml index 691424d69f..e54bc85c89 100644 --- a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml +++ b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml @@ -120,6 +120,9 @@ stages: - ${{ if not(or(parameters.buildSqlServerServer, parameters.buildSqlClient, parameters.buildAkvProvider)) }}: - job: publish_symbols_noop displayName: "No symbols to publish" + # Pool schema docs (OneBranch): https://aka.ms/obpipelines/yaml/jobs + # Agent pool concepts (Azure Pipelines): + # https://learn.microsoft.com/azure/devops/pipelines/agents/pools-queues pool: type: linux steps: diff --git a/eng/pipelines/onebranch/stages/release-stage.yml b/eng/pipelines/onebranch/stages/release-stage.yml index 65293c566e..127a5add3a 100644 --- a/eng/pipelines/onebranch/stages/release-stage.yml +++ b/eng/pipelines/onebranch/stages/release-stage.yml @@ -29,10 +29,6 @@ parameters: # ── General parameters ───────────────────────────────────────────────── - # True to enable debug information and steps. - - name: debug - type: boolean - # True for a OneBranch Official pipeline, false for a Non-Official pipeline. - name: isOfficial type: boolean diff --git a/eng/pipelines/onebranch/stages/validation-stage.yml b/eng/pipelines/onebranch/stages/validation-stage.yml index e8ad9849de..1b68d47416 100644 --- a/eng/pipelines/onebranch/stages/validation-stage.yml +++ b/eng/pipelines/onebranch/stages/validation-stage.yml @@ -31,10 +31,6 @@ parameters: - name: isOfficial type: boolean - # True if this is a preview build. - - name: isPreview - type: boolean - # True to verify that symbols are available on symbol servers. - name: publishSymbols type: boolean @@ -60,6 +56,8 @@ stages: - build_dependent - ${{ if eq(parameters.buildAKVProvider, true) }}: - build_addons + - ${{ if eq(parameters.publishSymbols, true) }}: + - publish_symbols jobs: # ── Signed-package validation ────────────────────────────────── @@ -69,8 +67,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/validate-signed-package-job.yml@self parameters: artifactName: $(sqlClientArtifactsName) + expectedFileVersion: $(sqlClientFileVersion) + expectedPackageVersion: $(sqlClientPackageVersion) isOfficial: ${{ parameters.isOfficial }} - isPreview: ${{ parameters.isPreview }} # ── Symbol-server validation ─────────────────────────────────── - ${{ if eq(parameters.publishSymbols, true) }}: diff --git a/eng/pipelines/onebranch/steps/publish-symbols-step.yml b/eng/pipelines/onebranch/steps/publish-symbols-step.yml index f8d9dc1ca6..ca4ca5175f 100644 --- a/eng/pipelines/onebranch/steps/publish-symbols-step.yml +++ b/eng/pipelines/onebranch/steps/publish-symbols-step.yml @@ -59,16 +59,16 @@ parameters: - name: searchPattern type: string - # Root folder to search for PDB files. When called from a build job this is typically - # $(BUILD_OUTPUT); when called from the dedicated symbols stage it points at the - # downloaded artifact path containing the PDBs. - - name: symbolsFolder - type: string - default: '$(BUILD_OUTPUT)' - - # Account/org where the symbols will be uploaded - - name: uploadAccount - type: string + # Root folder to search for PDB files. When called from a build job this is typically + # $(BUILD_OUTPUT); when called from the dedicated symbols stage it points at the + # downloaded artifact path containing the PDBs. + - name: symbolsFolder + type: string + default: '$(BUILD_OUTPUT)' + + # Account/org where the symbols will be uploaded + - name: uploadAccount + type: string # Version of the symbols to publish, typically the same as the NuGet package version - name: version