Skip to content

Add Unit and Functional test CI pipeline - #4506

Open
paulmedynski wants to merge 18 commits into
mainfrom
dev/paul/sqlclient-ci-unit
Open

Add Unit and Functional test CI pipeline#4506
paulmedynski wants to merge 18 commits into
mainfrom
dev/paul/sqlclient-ci-unit

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a package-triggered sqlclient-ci-unit pipeline for the SqlClient Unit and Functional test suites.

I decided not to bother adding separate pipelines for unit, functional, and simulated tests right now. We can decide if such a distinction is worth it later.

  • Runs net462 on Windows with native SNI.
  • Runs net8.0, net9.0, and net10.0 on Windows with native and managed SNI.
  • Runs net8.0, net9.0, and net10.0 on Linux and macOS with managed SNI.
  • Uses ADO-Win25, ADO-UB24, and the Microsoft-hosted macos-latest image.
  • Consumes the exact driver packages produced by the triggering sqlclient-ci-package run.
  • Excludes flaky tests through the existing build.proj default filter.

SNI coverage

The Windows managed-SNI legs configure the driver to use ManagedSni. The implementation is chosen by LocalAppContextSwitches.UseManagedNetworking, which reads the Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows AppContext switch. This pipeline sets that switch through the test host's runtimeconfig.json via a new UseManagedSniOnWindows build property, so it applies at process start, before the driver caches the value.

Writing config.jsonc does not work for these suites: that file is read only by DataTestUtility (ManualTests), the Azure extension tests, and PerformanceTests. This matters because the Unit suite holds the densest SNI coverage in the repo — SimulatedServerTests open real TCP connections to an in-process TdsServer, including SNICloseDeadlockTest, SNICloseRaceDeadlockTest, and SNICloseHandshakeCancellationTest.

The legacy CI and PR pipelines share the same limitation: they pass UseManagedSNIOnWindows into config.jsonc, but run Unit, Functional, and Manual in a single job, so only the Manual portion actually switches implementations. They are intentionally left unchanged, because this new CI unit pipeline closes that gap for CI. The modern PR pipeline has no SNI dimension at all for these suites; that is tracked separately in #4605.

Testing

  • Public project:
    • sqlclient-ci-package: 23906
    • sqlclient-ci-unit: 23904 — all 13 legs green, including the 3 Windows managed-SNI legs
  • ADO.Net project:
    • sqlclient-ci-package: 23904
    • sqlclient-ci-unit: 23901 — all 13 legs green, including the 3 Windows managed-SNI legs
    • sqlclient-ci-kerberos: 20260827.6
    • sqlclient-ci-managed-instance: 23907

Copilot AI lite review requested due to automatic review settings August 5, 2026 10:37
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Azure DevOps pipeline (sqlclient-ci-unit) that runs the SqlClient Unit and Functional test suites in Package reference mode, consuming the exact NuGet artifacts produced by the triggering sqlclient-ci-package run. This follows the same downstream-consume-upstream-artifacts pattern introduced by the stacked PR (#4499) and extends it to broad Unit/Functional coverage across OS/TFM/SNI combinations.

Changes:

  • Introduces a package-triggered pipeline definition that runs only on completion of sqlclient-ci-package (no PR/CI triggers).
  • Defines a 3-stage OS matrix (Windows/Linux/macOS) with the intended TFM and SNI coverage.
  • Adds a reusable job template that aligns source to the upstream commit, downloads/stages driver packages, runs Unit + Functional suites, and publishes results/artifacts.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
eng/pipelines/ci/unit/sqlclient-ci-unit-stages.yml Defines the Windows/Linux/macOS runtime/SNI job matrix for the unit+functional test pipeline.
eng/pipelines/ci/unit/sqlclient-ci-unit-pipeline.yml New pipeline entrypoint that is triggered by sqlclient-ci-package completion and invokes the stage matrix.
eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml Job template to align to upstream commit, consume produced packages, run Unit/Functional tests, and publish results.

Copilot AI review requested due to automatic review settings August 5, 2026 10:42
@paulmedynski paulmedynski added the Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. label Aug 5, 2026
@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board Aug 5, 2026
@paulmedynski paulmedynski added this to the 7.1.0-preview3 milestone Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml:100

  • The comment says this step downloads the exact driver packages, but sqlServerVersionOverride: 1.0.0 means $(sqlServerPackageVersion) will be pinned instead of using the version produced by the triggering sqlclient-ci-package run. That makes the current comment misleading and obscures why the override exists.

Update the comment to reflect the pin (or remove the override if the intent is to validate the exact SqlServer package from the upstream run).

      # Download the exact driver packages produced by the triggering pipeline.
      - template: /eng/pipelines/common/steps/download-driver-packages-step.yml@self
        parameters:
          sqlServerVersionOverride: 1.0.0

eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml:120

  • update-config-file-step.yml is being used only to set UseManagedSNIOnWindows, but because most parameters are omitted (so they default to empty/false), this step also overwrites the non-empty defaults in config.default.jsonc (e.g., TCP/NP connection strings and SupportsIntegratedSecurity=true). That unintentionally changes the baseline test config for this pipeline.

Pass through the defaults from config.default.jsonc so this step doesn’t clobber them while toggling SNI.

      # Configure the test suite's Windows SNI implementation.
      - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml@self
        parameters:
          debug: ${{ parameters.debug }}
          saPassword: ''
          UseManagedSNIOnWindows: ${{ parameters.useManagedSNI }}

Copilot AI review requested due to automatic review settings August 5, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 5, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml:8

  • The header comment claims this job runs against the exact packages from the triggering sqlclient-ci-package run, but the job forces sqlServerPackageVersion via sqlServerVersionOverride: 1.0.0, so Microsoft.SqlServer.Server is not necessarily taken from the upstream artifact (NU1605 workaround). Please adjust the comment to reflect this exception to avoid misleading future maintainers.
# Builds and runs the SqlClient Unit and Functional test suites in Package reference mode against
# the exact packages produced by the triggering sqlclient-ci-package run.

Copilot AI review requested due to automatic review settings August 5, 2026 11:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml:107

  • This job sets sqlServerVersionOverride: 1.0.0 while the adjacent comment says it downloads the “exact driver packages”. Since the override intentionally diverges from the artifact-resolved version (to avoid NU1605 downgrade warnings), add an in-file explanation (similar to managed-instance/stress jobs) so the behavior is clear.
      # Download the exact driver packages produced by the triggering pipeline.
      - template: /eng/pipelines/common/steps/download-driver-packages-step.yml@self
        parameters:
          sqlServerVersionOverride: 1.0.0

eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml:7

  • The header comment says this job runs against the “exact packages produced” by the upstream run, but this job pins Microsoft.SqlServer.Server via sqlServerVersionOverride (so at least that package version is not taken from the upstream artifact). Update the comment to reflect the exception so future readers aren’t misled.

This issue also appears on line 104 of the same file.

# Builds and runs the SqlClient Unit and Functional test suites in Package reference mode against
# the exact packages produced by the triggering sqlclient-ci-package run.

Comment thread eng/pipelines/ci/kerberos/sqlclient-ci-kerberos-job.yml
Comment thread eng/pipelines/ci/unit/sqlclient-ci-unit-job.yml
Comment thread eng/pipelines/common/templates/steps/publish-test-results-step.yml
Comment thread eng/pipelines/common/templates/steps/publish-test-results-step.yml
@paulmedynski
paulmedynski marked this pull request as ready for review August 5, 2026 12:52
@paulmedynski
paulmedynski requested a review from a team as a code owner August 5, 2026 12:52
@paulmedynski paulmedynski moved this from In progress to In review in SqlClient Board Aug 5, 2026

@priyankatiwari08 priyankatiwari08 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design looks right. Two non-blocking things:

1. Coverage is collected but never consumed. TestCodeCoverage defaults to true (build.proj:232) and sqlclient-ci-unit-job.yml doesn't override it, so all 13 legs pay the collector cost and publish .coverage artifacts — but this pipeline has no coverage stage. Either pass -p:TestCodeCoverage=false or wire in the merge stage.

2. Artifact name collides on job re-run. The shared publish template uses artifact: '${{ parameters.targetFramework }}WinAz$(System.JobId)'. System.JobId is stable across attempts, so re-running one failed leg fails with "Artifact ... already exists for build". eng/pipelines/pr/steps/publish-test-results-step.yml already appends $(System.JobAttempt) for exactly this reason. Pre-existing, but a 13-leg matrix is where it starts to bite.

Nit: the csprojs compare '$(UseManagedSniOnWindows)' == 'true' case-sensitively while build.proj uses .ToLower(), so a hand-run -p:UseManagedSniOnWindows=True would silently no-op.

Base automatically changed from dev/paul/sqlclient-ci-kerberos to main September 3, 2026 15:06
The signed sqlclient-ci-package driver assemblies grant InternalsVisibleTo
to test assemblies signed with the dedicated test key's public key. Download
the sqlclient-test-key.snk secure file and expose it as TestSigningKeyPath so
build.proj signs the unit-test assemblies accordingly.
Promote the target-framework loop from the managed-instance job up into the
stage, so each OS/SNI x runtime combination runs as its own parallel job with a
single Unit/Functional/Manual test pass (job/display names now include the TFM).

Add a sqlServerVersionOverride parameter to the shared download-driver-packages
step and pass 1.0.0 from the managed-instance and stress jobs, so restore uses
the released stable Microsoft.SqlServer.Server instead of the -ci prerelease and
avoids the NU1605 downgrade against Microsoft.SqlServer.Types' >= 1.0.0
dependency.  Overall package versioning is being addressed separately.
Replace the update-config-file-step call in the unit job with a
UseManagedSNIOnWindows build property that sets the
Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows AppContext switch
in the test host's runtime configuration.

config.jsonc is only read by ManualTests, the Azure extension tests, and
PerformanceTests, so the previous invocation never toggled SNI for the Unit and
Functional suites while still rewriting unrelated config fields with defaults.
Setting the switch via runtimeconfig applies it at process start, before the
driver caches its value.

Also restrict the sqlclient-ci-package completion trigger to main and
internal/main, matching the stress and Kerberos pipelines.

Refs #4605
Rename the UseManagedSNIOnWindows build property to UseManagedSniOnWindows and
the unit pipeline's useManagedSNI parameter to useManagedSni, matching the
repository's camel-case convention for abbreviations in identifiers.
TestDefaultAppContextSwitchValues asserted that UseManagedNetworking is false on
Windows, which fails in the managed SNI test leg now that the switch is enabled
process-wide through runtimeconfig.json. Compare the property against AppContext
instead, so the assertion still verifies that the switch is honoured and remains
unchanged when the switch is not configured.
@paulmedynski
paulmedynski force-pushed the dev/paul/sqlclient-ci-unit branch from f2bebd4 to b5ac613 Compare September 3, 2026 15:06
…args

Rename the test results artifact in the shared publish template to
TFM_Platform_job$(System.JobId)_attempt$(System.JobAttempt). This drops the
inaccurate "WinAz" infix, which was introduced with the initial YAML CI
pipeline and claimed Windows even for Linux and macOS legs, and adds the job
attempt number. System.JobId is stable across attempts, so re-running a
failed leg previously failed with "Artifact ... already exists for build".

Remove -p:PackageVersionAbstractions and -p:PackageVersionLogging from the
Functional Tests task in the unit CI job. Neither variable is defined by
download-driver-packages-step.yml, which only sets sqlClientPackageVersion
and sqlServerPackageVersion, so the empty macros could override build.proj's
versioning during restore.
Copilot AI review requested due to automatic review settings September 3, 2026 15:21
@paulmedynski

Copy link
Copy Markdown
Contributor Author

@priyankatiwari08 — thanks for the review. Addressed in 583af94. Taking your three points in order:

1. Coverage is collected but never consumed. Intentional, keeping it on. There will never be a coverage merge/publish stage in this pipeline. The design is for every upstream CI pipeline to collect and publish its own .coverage artifacts, and for an entirely separate downstream pipeline to gather them across all upstream CI runs and do the merging and publishing. That's why the shared publish step already renames the .coverage files by TFM before uploading. No change.

2. Artifact name collides on job re-run. Good catch, fixed. While in there I also dropped the WinAz infix, which has been inaccurate since the initial YAML CI pipeline landed — it asserted Windows even on the Linux and macOS legs — and otherwise carried no information. The shared template now publishes to:

${{ parameters.targetFramework }}_${{ parameters.operatingSystem }}_job$(System.JobId)_attempt$(System.JobAttempt)

e.g. net9.0_Windows_job<guid>_attempt1. Re-running a single failed leg no longer trips "Artifact ... already exists for build", and the Kerberos and Managed Instance jobs pick up the same fix since they share the template. Nothing in the repo consumed the old name. This matters a bit beyond cosmetics given point 1: the artifact name is effectively the interface the downstream coverage-merge pipeline will consume, so it now carries TFM and OS attribution instead of an opaque job GUID.

3. Nit: case-sensitive UseManagedSniOnWindows comparison. No change needed here — MSBuild's == operator inside Condition performs a case-insensitive string comparison, so a hand-run -p:UseManagedSniOnWindows=True still matches '$(UseManagedSniOnWindows)' == 'true' in both csprojs. Confirmed with a scratch project: <Val>True</Val> satisfies Condition="'$(Val)' == 'true'". The .ToLower() in build.proj is belt-and-braces rather than load-bearing.

Separately, while checking the version arguments I found the Functional Tests task was passing -p:PackageVersionAbstractions=$(abstractionsPackageVersion) and -p:PackageVersionLogging=$(loggingPackageVersion), but neither variable is defined anywhere — download-driver-packages-step.yml only sets sqlClientPackageVersion and sqlServerPackageVersion. Those empty macros could override build.proj's versioning during restore, so I removed them; the Functional task now matches the Unit task.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new UseManagedSniOnWindows MSBuild conditions in the test csproj files are case-sensitive and can silently fail to enable the runtimeconfig switch when the property is passed as a boolean-style value (e.g., True).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 11/12 changed files
  • Comments generated: 2
  • Review effort level: Lite

@cheenamalhotra cheenamalhotra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually would like to revisit this with new pipeline architecture to avoid introducing new agent overhead right now. Let's revisit this outside of 7.1.0 milestone?

@paulmedynski

Copy link
Copy Markdown
Contributor Author

Ok, moved to 8.0.0-preview1. The entire sqlclient-ci-unit pipeline runs in 10 minutes, and it adds missing coverage for Managed SNI, so I think it's valuable to prioritize once the current set of releases is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems.

Projects

Status: Waiting for customer

Development

Successfully merging this pull request may close these issues.

7 participants