diff --git a/.editorconfig b/.editorconfig index f0ea20ec32..ff6d9f3bd7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,9 @@ indent_size = 4 [*.{json,jsonc}] indent_size = 2 +[*.{yml,yaml}] +indent_size = 2 + # C# files [*.cs] # New line preferences diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml index 5cff58cd4e..7568f01608 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml @@ -3,6 +3,11 @@ # The .NET Foundation licenses this file to you under the MIT license. # # See the LICENSE file in the project root for more information. # ################################################################################# + +# This step configures an existing SQL Server running on the local Linux host. +# For example, our 1ES Hosted Pool has images like ADO-UB20-SQL22 that come with +# SQL Server 2022 pre-installed and running. + parameters: - name: password type: string diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml index 3e83d6b830..8899b9e68f 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml @@ -3,6 +3,10 @@ # The .NET Foundation licenses this file to you under the MIT license. # # See the LICENSE file in the project root for more information. # ################################################################################# + +# This step installs the latest SQL Server 2022 onto the macOS host and +# configures it for use. + parameters: - name: password type: string @@ -13,7 +17,7 @@ parameters: default: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) steps: -# Linux only steps +# macOS only steps - bash: | # The "user" pipeline variable conflicts with homebrew, causing errors during install. Set it back to the pipeline user. USER=`whoami` diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml index d159191b01..6586450e2e 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml @@ -3,6 +3,11 @@ # The .NET Foundation licenses this file to you under the MIT license. # # See the LICENSE file in the project root for more information. # ################################################################################# + +# This step configures an existing SQL Server running on the local Windows host. +# For example, our 1ES Hosted Pool has images like ADO-MMS22-SQL22 that come +# with SQL Server 2022 pre-installed and running. + parameters: # Windows only parameters - name: instanceName @@ -63,7 +68,7 @@ parameters: default: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) steps: -# windows only steps +# Windows only steps - powershell: | try { diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 4fa8c6bcbc..b7d30b31ea 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -65,10 +65,22 @@ parameters: - Project - Package +- name: buildConfiguration + displayName: 'Build Configuration' + default: Release + values: + - Release + - Debug + - name: defaultPoolName type: string default: $(ci_var_defaultPoolName) +- name: enableStressTests + displayName: Enable Stress Tests + type: boolean + default: false + variables: - template: libraries/ci-build-variables.yml@self @@ -84,6 +96,7 @@ stages: jobs: - template: common/templates/jobs/ci-build-nugets-job.yml@self parameters: + configuration: ${{ parameters.buildConfiguration }} artifactName: $(artifactName) ${{if ne(parameters.SNIVersion, '')}}: prebuildSteps: @@ -92,6 +105,16 @@ stages: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} + - ${{ if eq(parameters.enableStressTests, true) }}: + - template: stages/stress-tests-ci-stage.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + dependsOn: [build_nugets] + pipelineArtifactName: $(artifactName) + mdsPackageVersion: $(NugetPackageVersion) + ${{ if eq(parameters.debug, 'true') }}: + verbosity: 'detailed' + - template: common/templates/stages/ci-run-tests-stage.yml@self parameters: debug: ${{ parameters.debug }} @@ -139,7 +162,6 @@ stages: testConfigurations: windows_sql_19_x64: # configuration name pool: ${{parameters.defaultPoolName }} # pool name - hostedPool: false # whether the pool is hosted or not images: # list of images to run tests on Win22_Sql19: ADO-MMS22-SQL19 # stage display name: image name from the pool TargetFrameworks: ${{parameters.targetFrameworks }} #[net462, net8.0] # list of target frameworks to run @@ -181,7 +203,6 @@ stages: windows_sql_19_x86: # configuration name pool: ${{parameters.defaultPoolName }} # pool name - hostedPool: false # whether the pool is hosted or not images: # list of images to run tests on Win22_Sql19_x86: ADO-MMS22-SQL19 # stage display name: image name from the pool TargetFrameworks: [net8.0] #[net462, net8.0] # list of target frameworks to run diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 4956b15c89..ae01d2e9db 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -81,6 +81,18 @@ parameters: # parameters are shown up in ADO UI in a build queue time - Project - Package +- name: buildConfiguration + displayName: 'Build Configuration' + default: Release + values: + - Release + - Debug + +- name: enableStressTests + displayName: Enable Stress Tests + type: boolean + default: false + extends: template: dotnet-sqlclient-ci-core.yml@self parameters: @@ -92,3 +104,5 @@ extends: useManagedSNI: ${{ parameters.useManagedSNI }} codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} buildType: ${{ parameters.buildType }} + buildConfiguration: ${{ parameters.buildConfiguration }} + enableStressTests: ${{ parameters.enableStressTests }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index ecdaacfafb..97a7a5af24 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -73,6 +73,18 @@ parameters: # parameters are shown up in ADO UI in a build queue time - Project - Package +- name: buildConfiguration + displayName: 'Build Configuration' + default: Release + values: + - Release + - Debug + +- name: enableStressTests + displayName: Enable Stress Tests + type: boolean + default: false + extends: template: dotnet-sqlclient-ci-core.yml@self parameters: @@ -84,3 +96,5 @@ extends: useManagedSNI: ${{ parameters.useManagedSNI }} codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} buildType: ${{ parameters.buildType }} + buildConfiguration: ${{ parameters.buildConfiguration }} + enableStressTests: ${{ parameters.enableStressTests }} diff --git a/eng/pipelines/jobs/stress-tests-ci-job.yml b/eng/pipelines/jobs/stress-tests-ci-job.yml new file mode 100644 index 0000000000..2e01470fe5 --- /dev/null +++ b/eng/pipelines/jobs/stress-tests-ci-job.yml @@ -0,0 +1,214 @@ +################################################################################ +# 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. +################################################################################ + +# This stage builds and runs stress tests against an MDS NuGet package available +# as a pipeline artifact. +# +# The stress tests are located here: +# +# src/Microsoft.Data.SqlClient/tests/StressTests +# +# This template defines a job named 'run_stress_tests_job_' that can be +# depended on by downstream jobs. + +parameters: + # The suffix to append to the job name. + - name: jobNameSuffix + type: string + default: '' + + # The prefix to prepend to the job's display name: + # + # [] Run Stress Tests + # + - name: displayNamePrefix + type: string + default: '' + + # The name of the Azure Pipelines pool to use. + - name: poolName + type: string + default: '' + + # The pool VM image to use. + - name: vmImage + type: string + default: '' + + # The pipeline step to run to configure SQL Server. + # + # This step is expected to require no parameters. It must configure a SQL + # Server instance listening on localhost for SQL auth via the 'sa' user with + # the pipeline variable $(Password) as the password. + - name: sqlSetupStep + type: string + default: '' + + # The name of the pipeline artifact to download that contains the MDS package + # to stress test. + - name: pipelineArtifactName + type: string + default: '' + + # The solution file to restore/build. + - name: solution + type: string + default: '' + + # The test project to run. + - name: testProject + type: string + default: '' + + # dotnet CLI arguments for the restore step. + - name: restoreArguments + type: string + default: '' + + # dotnet CLI arguments for the build and run steps. + - name: buildArguments + type: string + default: '' + + # The list of .NET runtimes to test against. + - name: netTestRuntimes + type: object + default: [] + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkTestRuntimes + type: object + default: [] + + # The stress test config file contents to write to the config file. + # + # This should point to the SQL Server configured via the sqlSetupStep + # parameter, with user 'sa' and password of $(Password). + - name: configContent + type: string + default: '' + +jobs: +- job: run_stress_tests_job_${{ parameters.jobNameSuffix }} + displayName: '[${{ parameters.displayNamePrefix }}] Run Stress Tests' + pool: + name: ${{ parameters.poolName }} + ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: + vmImage: ${{ parameters.vmImage }} + ${{ else }}: + demands: + - imageOverride -equals ${{ parameters.vmImage }} + + variables: + # Stress test command-line arguments. + - name: testArguments + value: -a SqlClient.Stress.Tests -console + + # Explicitly unset the $PLATFORM environment variable that is set by the + # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. + # This is defined with a non-standard Platform of 'AnyCPU', and will fail + # the builds if left defined. The stress tests solution does not require + # any specific Platform, and so its solution file doesn't support any + # non-standard platforms. + # + # Note that Azure Pipelines will inject this variable as PLATFORM into the + # environment of all tasks in this job. + # + # See: + # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch + # + - name: Platform + value: '' + + # Do the same for $CONFIGURATION since we explicitly set it using our + # 'buildConfiguration' parameter, and we don't want the environment to + # override us. + - name: Configuration + value: '' + + steps: + + # Install the .NET 9.0 SDK. + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK + inputs: + packageType: sdk + version: 9.x + + # Install the .NET 8.0 runtime. + - task: UseDotNet@2 + displayName: Install .NET 8.0 Runtime + inputs: + packageType: runtime + version: 8.x + + # Download the pipeline artifact that contains the MDS package to test. + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifact + inputs: + artifactName: ${{ parameters.pipelineArtifactName }} + # The stress tests solution has a NuGet.config file that configures + # sources to look in this packages/ directory. + targetPath: $(Build.SourcesDirectory)/packages + + # Setup the local SQL Server. + - template: ${{ parameters.sqlSetupStep }}@self + + # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't support + # all of our argument combinations for the different build steps. + + # Restore the solution. + - task: DotNetCoreCLI@2 + displayName: Restore Solution + inputs: + command: custom + custom: restore + projects: ${{ parameters.solution }} + arguments: ${{ parameters.restoreArguments }} + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: ${{ parameters.solution }} + arguments: ${{ parameters.buildArguments }} --no-restore + + # Write the config file. + - task: PowerShell@2 + displayName: Write Config File + inputs: + pwsh: true + targetType: inline + script: | + # Capture the multi-line JSON content into a variable. + $content = @" + ${{ parameters.configContent }} + "@ + + # Write the JSON content to the config file. + $content | Out-File -FilePath "config.json" + + # Run the stress tests for each .NET runtime. + - ${{ each runtime in parameters.netTestRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{runtime}}] + inputs: + command: custom + custom: run + projects: ${{ parameters.testProject }} + arguments: ${{ parameters.buildArguments }} --no-build -f ${{runtime}} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) + + # Run the stress tests for each .NET Framework runtime. + - ${{ each runtime in parameters.netFrameworkTestRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{runtime}}] + inputs: + command: custom + custom: run + projects: ${{ parameters.testProject }} + arguments: ${{ parameters.buildArguments }} --no-build -f ${{runtime}} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml new file mode 100644 index 0000000000..06b41cd421 --- /dev/null +++ b/eng/pipelines/stages/stress-tests-ci-stage.yml @@ -0,0 +1,191 @@ +################################################################################ +# 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. +################################################################################ + +# This stage builds and runs stress tests against an MDS NuGet package available +# as a pipeline artifact. +# +# The stress tests are located here: +# +# src/Microsoft.Data.SqlClient/tests/StressTests +# +# All tests use a localhost SQL Server configured for SQL auth via the 'sa' user +# and password of '$(Password)'. The $(Password) variable is defined in the ADO +# Library "ADO Test Configuration properties", brought in by +# common/templates/libraries/ci-build-variables.yml. +# +# This template defines a stage named 'run_stress_tests_stage' that can be +# depended on by downstream stages. + +parameters: + # The type of build to produce (Release or Debug) + - name: buildConfiguration + displayName: Build Configuration + type: string + default: Release + values: + - Release + - Debug + + # The names of any stages this stage depends on, for example the stages + # that publish the MDS package artifacts we will test. + - name: dependsOn + displayName: Depends On Stages + type: object + default: [] + + # The name of the pipeline artifact to download that contains the MDS package + # to stress test. + - name: pipelineArtifactName + displayName: Pipeline Artifact Name + type: string + default: Artifacts + + # The MDS package version to stress test. This version must be available in + # one of the configured NuGet sources. + - name: mdsPackageVersion + displayName: MDS Package Version + type: string + default: '' + + # The list of .NET runtimes to test against. + - name: netTestRuntimes + displayName: .NET Test Runtimes + type: object + default: [net8.0, net9.0] + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkTestRuntimes + displayName: .NET Framework Test Runtimes + type: object + default: [net462, net47, net471, net472, net48, net481] + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + displayName: Dotnet CLI verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +stages: + - stage: run_stress_tests_stage + displayName: Run Stress Tests + dependsOn: ${{ parameters.dependsOn }} + + variables: + # The directory where dotnet artifacts will be staged. Not to be + # confused with pipeline artifact. + - name: dotnetArtifactsDir + value: $(Build.StagingDirectory)/dotnetArtifacts + + # The solution file to use for all dotnet CLI commands. + - name: solution + value: src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx + + # The stress test project to run. + - name: testProject + value: src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{parameters.verbosity}} + --artifacts-path $(dotnetArtifactsDir) + -p:MdsPackageVersion=${{parameters.mdsPackageVersion}} + + # dotnet CLI arguments for build/run commands. + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{parameters.buildConfiguration}} + + # The contents of the config file to use for all tests. We will write + # this to a JSON file for each test job, and then point to it via the + # STRESS_CONFIG_FILE environment variable. + - name: ConfigContent + value: | + [ + { + "name": "Azure SQL", + "type": "SqlServer", + "isDefault": true, + "dataSource": "localhost", + "user": "sa", + "password": "$(Password)", + "supportsWindowsAuthentication": false, + "isLocal": false, + "disableMultiSubnetFailover": true, + "disableNamedPipes": true, + "encrypt": false + } + ] + + jobs: + + # -------------------------------------------------------------------------- + # Build and test on Linux. + + - template: ../jobs/stress-tests-ci-job.yml@self + parameters: + jobNameSuffix: linux + displayNamePrefix: Linux + poolName: $(ci_var_defaultPoolName) + vmImage: ADO-UB20-SQL22 + sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml + pipelineArtifactName: ${{ parameters.pipelineArtifactName }} + solution: $(solution) + testProject: $(testProject) + restoreArguments: $(commonArguments) + buildArguments: $(buildArguments) + netTestRuntimes: ${{ parameters.netTestRuntimes }} + configContent: $(ConfigContent) + + # -------------------------------------------------------------------------- + # Build and test on Windows + + - template: ../jobs/stress-tests-ci-job.yml + parameters: + jobNameSuffix: windows + displayNamePrefix: Win + poolName: $(ci_var_defaultPoolName) + # The Windows images include a suitable .NET Framework runtime, so we + # don't have to install one explicitly. + vmImage: ADO-MMS22-SQL22 + sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml + pipelineArtifactName: ${{ parameters.pipelineArtifactName }} + solution: $(solution) + testProject: $(testProject) + restoreArguments: $(commonArguments) + buildArguments: $(buildArguments) + netTestRuntimes: ${{ parameters.netTestRuntimes }} + # Note that we include the .NET Framework runtimes for test runs on + # Windows. + netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} + configContent: $(ConfigContent) + + # -------------------------------------------------------------------------- + # Build and test on macOS. + + - template: ../jobs/stress-tests-ci-job.yml + parameters: + jobNameSuffix: macos + displayNamePrefix: macOS + # We don't have any 1ES Hosted Pool images for macOS, so we use a + # generic one from Azure Pipelines. + poolName: Azure Pipelines + vmImage: macos-latest + sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml + pipelineArtifactName: ${{ parameters.pipelineArtifactName }} + solution: $(solution) + testProject: $(testProject) + restoreArguments: $(commonArguments) + buildArguments: $(buildArguments) + netTestRuntimes: ${{ parameters.netTestRuntimes }} + configContent: $(ConfigContent) diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props index 958cc8e38f..66fbacae6c 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props @@ -1,6 +1,18 @@ - + + + + + + net462;net47;net471;net472;net48;net481;net8.0;net9.0 + + + latest + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props index b4f3c5f805..45b1a5018f 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props @@ -1,14 +1,38 @@ + + true true + + + + + $(MdsPackageVersion) + + + + + + + + + + 6.1.0-preview2.25178.5 + + + + - - + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj index 5d04960f79..0968e1837f 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.csproj @@ -2,7 +2,5 @@ Monitoring Monitoring - net481;net9.0 - latest diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config b/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config new file mode 100644 index 0000000000..19c2531f5d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md b/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md index 16ec09a874..f0723c3189 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md @@ -1,16 +1,30 @@ # Microsoft.Data.SqlClient Stress Test This Stress testing application for `Microsoft.Data.SqlClient` is under progress. -This project intends to help finding a certain level of effectiveness under unfavorable conditions, and verifying the mode of failures. -This is a console application with targeting frameworks `.Net Framework 4.8.1`, `.NET 9.0` under driver's supported operating systems and SQL Servers. + +This project intends to help finding a certain level of effectiveness under +unfavorable conditions, and verifying the mode of failures. + +This is a console application targeting all frameworks supported by MDS, +currently: + +- .NET 8.0 +- .NET 9.0 +- .NET Framework 4.6.2 +- .NET Framework 4.7 +- .NET Framework 4.7.1 +- .NET Framework 4.7.2 +- .NET Framework 4.8 +- .NET Framework 4.8.1 ## Purpose of application for developers -Define fuzz tests for all new features/APIs in the driver and to be run before every GA release. +Define fuzz tests for all new features/APIs in the driver and to be run before +every GA release. ## Pre-Requisites -Required in StressTest.config: +Required in the config file: |Field|Values|Description| |-|-|-| @@ -18,7 +32,6 @@ Required in StressTest.config: |`type`|`SqlServer`|Only `SqlServer` is acceptable.| |`isDefault`|`true`, `false`|If there is a source node with `isDefault=true`, this node is returned.| |`dataSource`||SQL Server data source name.| -|`database`||Targeting database name in the SQL Server.| |`user`||User Id to connect the server.| |`password`||Paired password with the user.| |`supportsWindowsAuthentication`|`true`, `false`|Tries to use integrated security in connection string mixed with SQL Server authentication if it set to `true` by applying the randomization.| @@ -27,49 +40,66 @@ Required in StressTest.config: |`disableNamedPipes`|`true`, `false`|`true` means the connections will create just using tcp protocol.| |`encrypt`|`true`, `false`|Assigns the encrypt property of the connection strings.| +Note: The database user must have permission to create and drop databases. +Each execution of the stress tests will create a database with a name like: + +- `StressTests-` + +The database will be dropped as a best effort once testing is complete. This +allows for multiple test runs to execute in parallel against the same database +server without colliding. + ## Adding new Tests - [ToDo] ## Building the application -To build the application we need to run the command: +To build the application using the `StressTests.slnx` solution: ```bash -dotnet build <-f|--framework > [-c|--configuration ] +dotnet build [-c|--configuration ] ``` -The path should be pointing to SqlClient.Stress.Runner.csproj file. - ```bash -# Builds the application for the Client Os in `Debug` Configuration for `AnyCpu` platform. -# All supported target frameworks, .NET Framework (NetFx) and .NET Core drivers are built by default (as supported by Client OS). +# Builds the application for the Client Os in `Debug` Configuration for `AnyCpu` +# platform. +# +# All supported target frameworks are built by default. -> dotnet build +$ dotnet build ``` ```bash # Build the application for .Net framework 4.8.1 with `Debug` configuration. -> dotnet build -f net481 +$ dotnet build -f net481 ``` ```bash # Build the application for .Net 9.0 with `Release` configuration. -> dotnet build -f net9.0 -c Release +$ dotnet build -f net9.0 -c Release ``` ```bash # Cleans all build directories -> dotnet clean +$ dotnet clean ``` ## Running tests -After building the application, find the built folder with target framework and run the `stresstest.exe` file with required arguments. -Find the result in a log file inside the `logs` folder besides the command prompt. +After building the application, find the built folder with target framework and +run the `stresstest.exe` file with required arguments. + +Find the result in a log file inside the `logs` folder besides the command +prompt. + +You may specify the config file by supplying an environment variable that +points to the file: + +- `STRESS_CONFIG_FILE=/path/to/my/config.jsonc` ## Command prompt @@ -81,10 +111,13 @@ directory (i.e. the same directory this readme file is in). $ cd /home/paul/dev/SqlClient/src/Microsoft.Data.SqlClient/tests/StressTests # Via dotnet run CLI: -$ dotnet run -no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests +$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests # Via dotnet CLI: $ dotnet SqlClient.Stress.Runner/bin/Debug/net9.0/stresstest.dll -a SqlClient.Stress.Tests + +# With a specific config file and all output to console: +$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -e STRESS_CONFIG_FILE=/path/to/config.jsonc -- -a SqlClient.Stress.Tests -console ``` ```powershell @@ -92,10 +125,13 @@ $ dotnet SqlClient.Stress.Runner/bin/Debug/net9.0/stresstest.dll -a SqlClient.St > cd \dev\SqlClient\src\Microsoft.Data.SqlClient\tests\StressTests # Via dotnet run CLI: -$ dotnet run -no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests +> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests # Via executable: > .\SqlClient.Stress.Runner\bin\Debug\net481\stresstest.exe -a SqlClient.Stress.Tests + +# With a specific config file and all output to console: +> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -e STRESS_CONFIG_FILE=c:\path\to\config.jsonc -- -a SqlClient.Stress.Tests -console ``` ## Supported arguments @@ -109,6 +145,7 @@ $ dotnet run -no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Str |-override|<name> <value>|Override the value of a test property.| |-test|<name1;name2>|Run specific test(s).| |-debug||Print process ID in the beginning and wait for Enter (to give your time to attach the debugger).| +|-console||Emit all output to the console instead of a log file.| |-exceptionThreshold|<n>|An optional limit on exceptions which will be caught. When reached, test will halt.| |-monitorenabled|true, false|True or False to enable monitoring. Default is false [not implemented]| |-randomSeed||Enables setting of the random number generator used internally. This serves both the purpose of helping to improve reproducibility and making it deterministic from Chess's perspective for a given schedule. Default is 0.| @@ -116,62 +153,72 @@ $ dotnet run -no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Str |-printMethodName||Print tests' title in console window| |-deadlockdetection|true, false|True or False to enable deadlock detection. Default is `false`.| -```bash -# Run the application for a built target framework and all discovered tests without debugger attached. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached. > .\stresstest.exe -a SqlClient.Stress.Tests -all ``` -```bash -# Run the application for a built target framework and all discovered tests without debugger attached and shows the test methods' names. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached and shows the test methods' names. > .\stresstest.exe -a SqlClient.Stress.Tests -all -printMethodName ``` -```bash -# Run the application for a built target framework and all discovered tests and will wait for debugger to be attached. +```powershell +# Run the application for a built target framework and all discovered tests and +# will wait for debugger to be attached. > .\stresstest.exe -a SqlClient.Stress.Tests -all -debug ``` -```bash -# Run the application for a built target framework and "TestExecuteXmlReaderAsyncCancellation" test without debugger attached. +```powershell +# Run the application for a built target framework and +# "TestExecuteXmlReaderAsyncCancellation" test without debugger attached. > .\stresstest.exe -a SqlClient.Stress.Tests -test TestExecuteXmlReaderAsyncCancellation ``` -```bash -# Run the application for a built target framework and "TestExecuteXmlReaderAsyncCancellation" test without debugger attached. +```powershell +# Run the application for a built target framework and +# "TestExecuteXmlReaderAsyncCancellation" test without debugger attached. > .\stresstest.exe -a SqlClient.Stress.Tests -test TestExecuteXmlReaderAsyncCancellation ``` -```bash -# Run the application for a built target framework and all discovered tests without debugger attached for 10 seconds. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached for 10 seconds. > .\stresstest.exe -a SqlClient.Stress.Tests -all -duration 10 ``` -```bash -# Run the application for a built target framework and all discovered tests without debugger attached with 5 threads. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached with 5 threads. > .\stresstest.exe -a SqlClient.Stress.Tests -all -threads 5 ``` -```bash -# Run the application for a built target framework and all discovered tests without debugger attached and dead lock detection process. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached and dead lock detection process. > .\stresstest.exe -a SqlClient.Stress.Tests -all -deadlockdetection true ``` -```bash -# Run the application for a built target framework and all discovered tests without debugger attached with overriding the weight property with value 15. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached with overriding the weight property with value 15. > .\stresstest.exe -a SqlClient.Stress.Tests -all -override Weight 15 ``` -```bash -# Run the application for a built target framework and all discovered tests without debugger attached with injecting random seed of 5. +```powershell +# Run the application for a built target framework and all discovered tests +# without debugger attached with injecting random seed of 5. > .\stresstest.exe -a SqlClient.Stress.Tests -all -randomSeed 5 ``` diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj index 6950cb6af8..5540d4951e 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/SqlClient.Stress.Common.csproj @@ -1,8 +1,5 @@ - - net481;net9.0 - latest - + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs index 5c0fd3e362..b61379aa0a 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs @@ -94,6 +94,8 @@ protected string GetOptionalAttributeValue(IDictionary propertie } return value; } + + public abstract void Emit(byte indent); } /// @@ -105,7 +107,6 @@ protected string GetOptionalAttributeValue(IDictionary propertie /// type="SqlServer" /// isDefault="false" /// dataSource="mysrv01" - /// database="stress" /// user="stress" /// password="" /// supportsWindowsAuthentication="false"> @@ -114,11 +115,17 @@ protected string GetOptionalAttributeValue(IDictionary propertie public class SqlServerDataSource : DataSource { public readonly string DataSource; - public readonly string Database; + public readonly string Database = "StressTests-" + Guid.NewGuid().ToString(); public readonly bool IsLocal; public readonly bool Encrypt; - // if user and password are set, test can create connection strings with SQL auth settings + // If EntraIdUser is set, the connection will use EntraID password-based + // authentication. + public readonly string EntraIdUser; + public readonly string EntraIdPassword; + + // If EntraIdUser isn't set, and User is set, the connection will use + // classic SQL user/password based authentication. public readonly string User; public readonly string Password; @@ -133,7 +140,9 @@ internal SqlServerDataSource(string name, bool isDefault, IDictionary public class DataStressSettings { + internal static readonly string s_defaultConfigFileName = "StressTests.config.jsonc"; - internal static readonly string s_configFileName = "StressTest.config"; - - // use Instance to access the settings - private DataStressSettings() + public DataStressSettings(string configFileName) { + _dataStressConfigSection = new(configFileName); } - private bool Initialized { get; set; } + private static DataStressSettings s_instance; - private DataStressConfigurationSection _dataStressSettings = new DataStressConfigurationSection(); + public static DataStressSettings Instance + { + get + { + if (s_instance is null) + { + var cfg = Environment.GetEnvironmentVariable("STRESS_CONFIG_FILE"); + if (cfg is null) + { + cfg = s_defaultConfigFileName; + } + s_instance = new(cfg); + } + + s_instance.Load(); + + return s_instance; + } + } + + private readonly DataStressConfigurationSection _dataStressConfigSection; // list of sources read from the config file private Dictionary _sources = new Dictionary(StringComparer.CurrentCultureIgnoreCase); @@ -48,26 +68,6 @@ public int NumberOfConnectionPools private set; } - // singleton instance, lazy evaluation - private static DataStressSettings s_instance = new DataStressSettings(); - public static DataStressSettings Instance - { - get - { - if (!s_instance.Initialized) - { - lock (s_instance) - { - if (!s_instance.Initialized) - { - s_instance.Load(); - } - } - } - return s_instance; - } - } - #region Configuration file handlers private class DataStressConfigurationSection @@ -75,8 +75,12 @@ private class DataStressConfigurationSection private List _sources = new List(); private ErrorHandlingPolicyElement _errorHandlingPolicy = new ErrorHandlingPolicyElement(); private ConnectionPoolPolicyElement _connectionPoolPolicy = new ConnectionPoolPolicyElement(); + private readonly StressConfigReader _reader; - StressConfigReader reader = new StressConfigReader(s_configFileName); + public DataStressConfigurationSection(string configFileName) + { + _reader = new StressConfigReader(configFileName); + } public List Sources { @@ -84,8 +88,8 @@ public List Sources { if(_sources.Count == 0) { - reader.Load(); - _sources = reader.Sources; + _reader.Load(); + _sources = _reader.Sources; } return _sources; } @@ -122,7 +126,8 @@ public DataSourceElement(string ds_name, string ds_type, string ds_server, string ds_datasource, - string ds_database, + string ds_entraIdUser, + string ds_entraIdPassword, string ds_user, string ds_password, bool ds_isDefault = false, @@ -145,9 +150,13 @@ public DataSourceElement(string ds_name, { SourceProperties.Add("dataSource", ds_datasource); } - if (ds_database != null) + if (ds_entraIdUser != null) { - SourceProperties.Add("database", ds_database); + SourceProperties.Add("entraIdUser", ds_entraIdUser); + } + if (ds_entraIdPassword != null) + { + SourceProperties.Add("entraIdPassword", ds_entraIdPassword); } if (ds_user != null) { @@ -164,7 +173,7 @@ public DataSourceElement(string ds_name, SourceProperties.Add("DisableMultiSubnetFailoverSetup", disableMultiSubnetFailoverSetup.ToString()); SourceProperties.Add("DisableNamedPipes", disableNamedPipes.ToString()); - + SourceProperties.Add("Encrypt", encrypt.ToString()); if (ds_dbFile != null) @@ -241,7 +250,7 @@ public int NumberOfPools private void Load() { // Parse - foreach (DataSourceElement sourceElement in _dataStressSettings.Sources) + foreach (DataSourceElement sourceElement in _dataStressConfigSection.Sources) { // if Parse raises exception, check that the type attribute is set to the relevant the SourceType enumeration value name DataSourceType sourceType = (DataSourceType)Enum.Parse(typeof(DataSourceType), sourceElement.Type, true); @@ -252,14 +261,12 @@ private void Load() // Parse // if Parse raises exception, check that the action attribute is set to a valid ActionOnProductBugFound enumeration value name - this.ActionOnProductError = (ErrorHandlingAction)Enum.Parse(typeof(ErrorHandlingAction), _dataStressSettings.ErroHandlingPolicy.OnProductError, true); - this.ActionOnTestError = (ErrorHandlingAction)Enum.Parse(typeof(ErrorHandlingAction), _dataStressSettings.ErroHandlingPolicy.OnTestError, true); - this.ActionOnProgrammingError = (ErrorHandlingAction)Enum.Parse(typeof(ErrorHandlingAction), _dataStressSettings.ErroHandlingPolicy.OnProgrammingError, true); + this.ActionOnProductError = (ErrorHandlingAction)Enum.Parse(typeof(ErrorHandlingAction), _dataStressConfigSection.ErroHandlingPolicy.OnProductError, true); + this.ActionOnTestError = (ErrorHandlingAction)Enum.Parse(typeof(ErrorHandlingAction), _dataStressConfigSection.ErroHandlingPolicy.OnTestError, true); + this.ActionOnProgrammingError = (ErrorHandlingAction)Enum.Parse(typeof(ErrorHandlingAction), _dataStressConfigSection.ErroHandlingPolicy.OnProgrammingError, true); // Parse - this.NumberOfConnectionPools = _dataStressSettings.ConnectionPoolPolicy.NumberOfPools; - - this.Initialized = true; + this.NumberOfConnectionPools = _dataStressConfigSection.ConnectionPoolPolicy.NumberOfPools; } diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs index 96f2d16d6c..ea2bfe6553 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs @@ -96,6 +96,8 @@ protected static DataSource Source [GlobalTestSetup] public virtual void GlobalTestSetup() { + Console.WriteLine("DataTestGroup.GlobalTestSetup(): Starting..."); + // Preconditions - ensure this setup is only called once DataStressErrors.Assert(string.IsNullOrEmpty(s_scenario), "Scenario was already set"); DataStressErrors.Assert(s_source == null, "Source was already set"); @@ -118,12 +120,22 @@ public virtual void GlobalTestSetup() // Set m_factory s_factory = CreateFactory(ref s_scenario, ref s_source); + + Console.WriteLine($"GlobalTestSetup factory created:"); + Console.WriteLine($" Scenario: {s_scenario}"); + Console.WriteLine($" Factory: {s_factory.GetType().Name}"); + Console.WriteLine($" Source:"); + s_source.Emit(4); + + s_factory.CreateDatabase(s_source); s_factory.InitializeSharedData(s_source); // Postconditions DataStressErrors.Assert(!string.IsNullOrEmpty(s_scenario), "Scenario was not set"); DataStressErrors.Assert(s_source != null, "Source was not set"); DataStressErrors.Assert(s_factory != null, "Factory was not set"); + + Console.WriteLine("DataTestGroup.GlobalTestSetup(): Finished"); } /// @@ -134,11 +146,16 @@ public virtual void GlobalTestSetup() [GlobalTestCleanup] public virtual void GlobalTestCleanup() { + Console.WriteLine("DataTestGroup.GlobalTestCleanup(): Starting..."); + s_factory.CleanupSharedData(); + s_factory.DropDatabase(s_source); s_source = null; s_scenario = null; s_factory.Dispose(); s_factory = null; + + Console.WriteLine("DataTestGroup.GlobalTestCleanup(): Finished"); } diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj index e76a05f52a..91fe0f81ee 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj @@ -2,8 +2,6 @@ Stress.Data - net481;net9.0 - latest @@ -14,10 +12,4 @@ - - - Always - - - diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs index 24344a04f9..1b677f965a 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; using System.Xml; using System.Xml.XPath; using static Stress.Data.DataStressSettings; @@ -15,7 +17,8 @@ namespace Stress.Data /// internal class StressConfigReader { - private string _configFilePath; + private readonly string _configFilePath; + private readonly bool _configIsJson; private const string dataStressSettings = "dataStressSettings"; private const string sourcePath = "//dataStressSettings/sources/source"; internal List Sources @@ -25,10 +28,92 @@ internal List Sources public StressConfigReader(string configFilePath) { - this._configFilePath = configFilePath; + _configFilePath = configFilePath; + + // If the config filename extension is 'json' or 'jsonc', we parse + // it as JSON. + if (configFilePath.EndsWith(".json", StringComparison.OrdinalIgnoreCase) || + configFilePath.EndsWith(".jsonc", StringComparison.OrdinalIgnoreCase)) + { + _configIsJson = true; + } + // Otherwise, parse it as XML. + else + { + _configIsJson = false; + + // The original code always prepended the Framework project + // directory onto whatever path was given, so we do the same if + // that isn't already present. + if (!_configFilePath.StartsWith("SqlClient.Stress.Framework/")) + { + _configFilePath = Path.Combine("SqlClient.Stress.Framework", _configFilePath); + } + } } internal void Load() + { + if (_configIsJson) + { + LoadJson(); + } + else + { + LoadXml(); + } + } + + private struct JsonDataSource + { + public string Name { get; set; } + public string Type { get; set; } + public bool IsDefault { get; set; } + public string DataSource { get; set; } + public string EntraIdUser { get; set; } + public string EntraIdPassword { get; set; } + public string User { get; set; } + public string Password { get; set; } + public bool SupportsWindowsAuthentication { get; set; } + public bool IsLocal { get; set; } + public bool DisableMultiSubnetFailover { get; set; } + public bool DisableNamedPipes { get; set; } + public bool Encrypt { get; set; } + } + + private void LoadJson() + { + var sources = JsonSerializer.Deserialize>( + File.ReadAllText(_configFilePath), + new JsonSerializerOptions() + { + IncludeFields = true, + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip + }); + + Sources = new(sources.Count); + + foreach (var source in sources) + { + Sources.Add(new DataSourceElement( + source.Name, + source.Type, + null, + source.DataSource, + source.EntraIdUser, + source.EntraIdPassword, + source.User, + source.Password, + ds_isDefault: source.IsDefault, + ds_isLocal: source.IsLocal, + disableMultiSubnetFailoverSetup: source.DisableMultiSubnetFailover, + disableNamedPipes: source.DisableNamedPipes, + encrypt: source.Encrypt)); + } + } + + private void LoadXml() { XmlReader reader = null; try @@ -52,7 +137,6 @@ internal void Load() string dataSource = sourceNavigator.GetAttribute("dataSource", nsUri); string user = sourceNavigator.GetAttribute("user", nsUri); string password = sourceNavigator.GetAttribute("password", nsUri); - string database = sourceNavigator.GetAttribute("database", nsUri); bool supportsWindowsAuthentication; supportsWindowsAuthentication = bool.TryParse(sourceNavigator.GetAttribute("supportsWindowsAuthentication", nsUri), out supportsWindowsAuthentication) ? supportsWindowsAuthentication : false; bool isLocal; @@ -64,7 +148,20 @@ internal void Load() bool encrypt; encrypt = bool.TryParse(sourceNavigator.GetAttribute("encrypt", nsUri), out encrypt) ? encrypt : false; - DataSourceElement element = new DataSourceElement(sourceName, sourceType, null, dataSource, database, user, password, ds_isDefault: isDefault, ds_isLocal: isLocal, disableMultiSubnetFailoverSetup: disableMultiSubnetFailover, disableNamedPipes: disableNamedPipes, encrypt: encrypt); + DataSourceElement element = new( + sourceName, + sourceType, + null, + dataSource, + string.Empty, + string.Empty, + user, + password, + ds_isDefault: isDefault, + ds_isLocal: isLocal, + disableMultiSubnetFailoverSetup: disableMultiSubnetFailover, + disableNamedPipes: disableNamedPipes, + encrypt: encrypt); Sources.Add(element); } } diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTests.config.jsonc b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTests.config.jsonc new file mode 100644 index 0000000000..b160d304a7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTests.config.jsonc @@ -0,0 +1,19 @@ +// Sample config file for the Stress Tests app. +[ + // Each array element is a "data source" object. + { + "name": "My Favourite SQL Server", + "type": "SqlServer", + "isDefault": true, + "dataSource": "127.0.0.1,1433", + "entraIdUser": "", + "entraIdPassword": "", + "user": "sa", + "password": "", + "supportsWindowsAuthentication": false, + "isLocal": false, + "disableMultiSubnetFailover": true, + "disableNamedPipes": true, + "encrypt": false + } +] diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTest.config b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTests.config.xml similarity index 95% rename from src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTest.config rename to src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTests.config.xml index 7a6a422924..a9cedadfa5 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTest.config +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressTests.config.xml @@ -7,7 +7,6 @@ type="SqlServer" isDefault="true" dataSource="127.0.0.1,1433" - database="master" user="sa" password="" supportsWindowsAuthentication="false" diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs index bc13ea7edc..6b49692aa7 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs @@ -7,15 +7,17 @@ using System.IO; using System.Reflection; +using Microsoft.Data.SqlClient; + namespace DPStressHarness//Microsoft.Data.SqlClient.Stress { class Program { private static bool s_debugMode = false; - static void Main(string[] args) + static int Main(string[] args) { Init(args); - Run(); + return Run(); } public enum RunMode @@ -30,6 +32,7 @@ public enum RunMode private static IEnumerable s_tests; private static StressEngine s_eng; private static string s_error; + private static bool s_console = false; public static void Init(string[] args) { @@ -70,6 +73,10 @@ public static void Init(string[] args) s_mode = RunMode.RunVerify; break; + case "-console": + s_console = true; + break; + case "-debug": s_debugMode = true; if (System.Diagnostics.Debugger.IsAttached) @@ -135,7 +142,7 @@ public static void Init(string[] args) } } - public static void Run() + public static int Run() { if (TestFinder.AssemblyName == null) { @@ -144,24 +151,21 @@ public static void Run() switch (s_mode) { case RunMode.RunAll: - RunStress(); - break; + return RunStress(); case RunMode.RunVerify: - RunVerify(); - break; + return RunVerify(); case RunMode.ExitWithError: - ExitWithError(); - break; + return ExitWithError(); case RunMode.Help: - PrintHelp(); - break; + default: + return PrintHelp(); } } - private static void PrintHelp() + private static int PrintHelp() { Console.WriteLine("stresstest.exe [-a ] "); Console.WriteLine(); @@ -181,6 +185,8 @@ private static void PrintHelp() Console.WriteLine(); Console.WriteLine(" -test Run specific test(s)."); Console.WriteLine(); + Console.WriteLine(" -console Emit all output to the console."); + Console.WriteLine(); Console.WriteLine(" -debug Print process ID in the beginning and wait for Enter (to give your time to attach the debugger)."); Console.WriteLine(); Console.WriteLine(" -exceptionThreshold An optional limit on exceptions which will be caught. When reached, test will halt."); @@ -198,24 +204,35 @@ private static void PrintHelp() Console.WriteLine(); Console.WriteLine(" -deadlockdetection True or False to enable deadlock detection. Default is false"); Console.WriteLine(); + + return 1; } private static void PrintConfigSummary() { - const int border = 140; - Console.WriteLine(new string('#', border)); - Console.WriteLine($"\t AssemblyName:\t{TestFinder.AssemblyName}"); - Console.WriteLine($"\t Run mode:\t{Enum.GetName(typeof(RunMode), s_mode)}"); - foreach (KeyValuePair item in TestMetrics.Overrides) Console.WriteLine($"\t Override:\t{item.Key} = {item.Value}"); - foreach (string item in TestMetrics.SelectedTests) Console.WriteLine($"\t Test:\t{item}"); - Console.WriteLine($"\t Duration:\t{TestMetrics.StressDuration} second(s)"); - Console.WriteLine($"\t Threads No.:\t{TestMetrics.StressThreads}"); - Console.WriteLine($"\t Debug mode:\t{s_debugMode}"); - Console.WriteLine($"\t Exception threshold:\t{TestMetrics.ExceptionThreshold}"); - Console.WriteLine($"\t Random seed:\t{TestMetrics.RandomSeed}"); - Console.WriteLine($"\t Filter:\t{TestMetrics.Filter}"); - Console.WriteLine($"\t Deadlock detection enabled:\t{DeadlockDetection.IsEnabled}"); - Console.WriteLine(new string('#', border)); + string border = new('#', 80); + + Console.WriteLine(border); + Console.WriteLine($"MDS Version: {GetMdsVersion()}"); + Console.WriteLine($"Test Assembly Name: {TestFinder.AssemblyName}"); + Console.WriteLine($"Run mode: {Enum.GetName(typeof(RunMode), s_mode)}"); + foreach (var item in TestMetrics.Overrides) + { + Console.WriteLine($"Override: {item.Key} = {item.Value}"); + } + foreach (var item in TestMetrics.SelectedTests) + { + Console.WriteLine($"Test: {item}"); + } + Console.WriteLine($"Duration: {TestMetrics.StressDuration} second(s)"); + Console.WriteLine($"Threads No.: {TestMetrics.StressThreads}"); + Console.WriteLine($"Emit to console: {s_console}"); + Console.WriteLine($"Debug mode: {s_debugMode}"); + Console.WriteLine($"Exception threshold: {TestMetrics.ExceptionThreshold}"); + Console.WriteLine($"Random seed: {TestMetrics.RandomSeed}"); + Console.WriteLine($"Filter: {TestMetrics.Filter}"); + Console.WriteLine($"Deadlock detection: {DeadlockDetection.IsEnabled}"); + Console.WriteLine(border); } private static int ExitWithError() @@ -231,7 +248,7 @@ private static int RunVerify() private static int RunStress() { - if (!s_debugMode) + if (!s_console) { try { @@ -249,5 +266,41 @@ private static int RunStress() } return s_eng.Run(); } + + private static string GetMdsVersion() + { + // MDS captures its NuGet package version at build-time, so pull + // it out and return it. + // + // See: tools/targets/GenerateThisAssemblyCs.targets + // + var assembly = typeof(SqlConnection).Assembly; + var type = assembly.GetType("System.ThisAssembly"); + if (type is null) + { + return ""; + } + + // Look for the NuGetPackageVersion field, which is available in + // newer MDS packages. + var field = type.GetField( + "NuGetPackageVersion", + BindingFlags.NonPublic | BindingFlags.Static); + + // If not present, use the older assembly file version field. + if (field is null) + { + field = type.GetField( + "InformationalVersion", + BindingFlags.NonPublic | BindingFlags.Static); + } + + if (field is null) + { + return ""; + } + + return (string)field.GetValue(null) ?? ""; + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj index 23aebda620..9689097795 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj @@ -3,10 +3,12 @@ Exe stresstest - net481;net9.0 - latest + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj index 6b03c2a5af..6eb77bec44 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj @@ -2,8 +2,6 @@ Stress.Data.SqlClient - net481;net9.0 - latest diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs index b8ea7def81..d5c8cd0000 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Diagnostics; using Microsoft.Data.SqlClient; using Microsoft.Test.Data.SqlClient; @@ -74,8 +75,6 @@ internal void Initialize(ref string scenario, ref DataSource source) } } - - internal void Terminate() { if (_multiSubnetSetupHelper != null) @@ -94,10 +93,16 @@ public override bool PrimaryKeyValueIsRequired get { return false; } } - public override string CreateBaseConnectionString(Random rnd, ConnectionStringOptions options) + { + return CreateBaseConnectionStringBuilder(rnd, options).ToString(); + } + + private SqlConnectionStringBuilder CreateBaseConnectionStringBuilder( + Random rnd, ConnectionStringOptions options) { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); + builder.ApplicationName = "StressTests"; switch (_scenario) { @@ -125,6 +130,12 @@ public override string CreateBaseConnectionString(Random rnd, ConnectionStringOp { builder.IntegratedSecurity = true; } + else if (_source.EntraIdUser.Length != 0) + { + builder.Authentication = SqlAuthenticationMethod.ActiveDirectoryPassword; + builder.UserID = _source.EntraIdUser; + builder.Password = _source.EntraIdPassword; + } else { builder.UserID = _source.User; @@ -212,7 +223,7 @@ public override string CreateBaseConnectionString(Random rnd, ConnectionStringOp builder.TrustServerCertificate = true; builder.MaxPoolSize = 1000; - return builder.ToString(); + return builder; } protected override int GetNumDifferentApplicationNames() @@ -222,5 +233,74 @@ protected override int GetNumDifferentApplicationNames() // because it reduces the amount of multithreadedness within each pool. return 1; } + + public override void CreateDatabase(DataSource source) + { + var database = (source as SqlServerDataSource).Database; + + Console.WriteLine($"Creating database [{database}]..."); + + var builder = CreateBaseConnectionStringBuilder( + null, ConnectionStringOptions.DisableMultiSubnetFailover); + builder.InitialCatalog = "master"; + + using SqlConnection connection = new(builder.ToString()); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = $"create database [{database}]"; + command.ExecuteNonQuery(); + + Console.WriteLine($"Created database [{database}]"); + } + + public override void DropDatabase(DataSource source) + { + var database = (source as SqlServerDataSource).Database; + + Console.WriteLine($"Dropping database [{database}]..."); + + var builder = CreateBaseConnectionStringBuilder( + null, ConnectionStringOptions.DisableMultiSubnetFailover); + builder.InitialCatalog = "master"; + + using SqlConnection connection = new(builder.ToString()); + connection.Open(); + + // Kill all connections currently using the database so we can drop + // it. + { + using SqlCommand command = connection.CreateCommand(); + command.CommandText = + $"select session_id from sys.dm_exec_sessions where database_id = DB_ID('{database}')"; + + List sessionIds = new(); + { + using var reader = command.ExecuteReader(); + while (reader.Read()) + { + sessionIds.Add(reader.GetInt16(0)); + } + } + + foreach (var sessionId in sessionIds) + { + using var killCommand = connection.CreateCommand(); + killCommand.CommandText = $"kill {sessionId}"; + Console.WriteLine($" Killing session {sessionId}..."); + killCommand.ExecuteNonQuery(); + Console.WriteLine($" Killed session {sessionId}"); + } + } + + // Drop the database. + { + using SqlCommand command = connection.CreateCommand(); + command.CommandText = $"drop database [{database}]"; + command.ExecuteNonQuery(); + } + + Console.WriteLine($"Dropped database [{database}]"); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs index b5b3b69e45..e48c13d49b 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs @@ -76,19 +76,27 @@ private static void ClearAllPoolsThreadFunc() public override void GlobalTestSetup() { + Console.WriteLine("SqlClientTestGroup.GlobalTestSetup(): Starting..."); + base.GlobalTestSetup(); s_clearAllPoolsThread = new Thread(ClearAllPoolsThreadFunc); s_clearAllPoolsThread.Start(); // set the notification options for SqlNotificationRequest tests - s_notificationOptions = "service=StressNotifications;local database=" + ((SqlServerDataSource)Source).Database; + var source = Source as SqlServerDataSource; + s_notificationOptions = "service=StressNotifications;local database=" + source.Database; + + s_sqlDependencyConnString = Factory.CreateBaseConnectionString( + null, DataStressFactory.ConnectionStringOptions.DisableMultiSubnetFailover); - s_sqlDependencyConnString = Factory.CreateBaseConnectionString(null, DataStressFactory.ConnectionStringOptions.DisableMultiSubnetFailover); + Console.WriteLine("SqlClientTestGroup.GlobalTestSetup(): Finished"); } public override void GlobalTestCleanup() { + Console.WriteLine("SqlClientTestGroup.GlobalTestCleanup(): Starting..."); + s_clearAllPoolsThreadStop.Set(); s_clearAllPoolsThread.Join(); @@ -99,6 +107,8 @@ public override void GlobalTestCleanup() } base.GlobalTestCleanup(); + + Console.WriteLine("SqlClientTestGroup.GlobalTestCleanup(): Finished"); } public override void GlobalExceptionHandler(Exception e) diff --git a/tools/targets/GenerateThisAssemblyCs.targets b/tools/targets/GenerateThisAssemblyCs.targets index 0bcdf8a2aa..6230af8ae7 100644 --- a/tools/targets/GenerateThisAssemblyCs.targets +++ b/tools/targets/GenerateThisAssemblyCs.targets @@ -14,6 +14,7 @@ namespace System internal static class ThisAssembly { internal const string InformationalVersion = "$(AssemblyFileVersion)"%3B +internal const string NuGetPackageVersion = "$(Version)"%3B } }