diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json
index e64cdddad..d94f6a5ba 100644
--- a/.fallout/build.schema.json
+++ b/.fallout/build.schema.json
@@ -33,6 +33,7 @@
"Docs",
"DocsReference",
"DocsSdk",
+ "IntegrationTest",
"Pack",
"PackBinaries",
"Publish",
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8c9c99a8d..373695a90 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,3 +37,23 @@ jobs:
- name: Run './build.cmd ci'
run: ./build.sh ci
+ # The SQL Server backed integration tests. Each test assembly starts its own SQL Server container with Testcontainers,
+ # which needs only the Docker daemon that ubuntu-latest provides.
+ integration-linux:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/setup-dotnet@v6
+ with:
+ dotnet-version: 10.0
+ - uses: actions/checkout@v7
+ - name: Run './build.sh Compile IntegrationTest'
+ run: ./build.sh Compile IntegrationTest
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: integration-test-results
+ path: artifacts/TestResults
+ if-no-files-found: ignore
+
diff --git a/AGENTS.md b/AGENTS.md
index 9aedf969a..b5577e310 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,7 +36,7 @@ dotnet test --project test/Spring/Spring.Core.Tests/Spring.Core.Tests.csproj -f
Fallout's `Test` target excludes `*.Integration.Tests` projects (and `Spring.Web.Conversation.NHibernate5.Tests`) by default; they need external infrastructure and are gated behind flags: `--test-full`, `--test-integration-data`, `--test-integration-nms`, `--test-integration-ms-mq`. Several test projects are Windows-only (net472: Web, Mvc5, Messaging/MSMQ).
-- SQL Server-backed tests (`Spring.Data.Integration.Tests`, `Spring.Data.NHibernate5.Integration.Tests`, `Spring.Scheduling.Quartz3.Integration.Tests`): `docker-compose.yml` brings up SQL Server; setup scripts in `build-support/*.sql` and the test projects' `Data/` folders. See README.md "Running tests" for the full recipe.
+- SQL Server-backed tests (`Spring.Data.Integration.Tests`, `Spring.Data.NHibernate5.Integration.Tests`, `Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests`, `Spring.Scheduling.Quartz3.Integration.Tests`) provision their own database with Testcontainers: a running Docker daemon is all they need. `./build.cmd IntegrationTest` runs them (CI: the `integration-linux` job); `--test-integration-data` adds them to `Test`. Each assembly's `[SetUpFixture]` calls `test/Shared/SqlServerTestEnvironment.cs` (a linked source file), which starts a pinned SQL Server container, runs `build-support/create-integration-test-databases-and-users.sql` and the project's `*_database.sql` scripts, and publishes `SPRING_TEST_SQLSERVER_*` environment variables that the XML contexts read as `${...}` placeholders. Setting `SPRING_TEST_SQLSERVER` to an admin connection string uses that server instead of a container. Tests that need MSDTC are `[Explicit]`. See README.md "Running tests".
## Architecture
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 80a9bf749..0c9c19a44 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -40,6 +40,7 @@
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 8473f64f0..f29a5cc21 100644
--- a/README.md
+++ b/README.md
@@ -158,17 +158,50 @@ suffice.
### Running tests
-- To run tests that require a database connection, you need:
- - SqlServer running in (local)
- - Check in `SQL Server Configuration Manager` that `Named Pipes` and `TCP/IP` protocols are enabled
- - From `build-support` execute
- - `create-integration-test-databases-and-users.sql`
- - From `\test\Spring\Spring.Data.Integration.Tests\Data` execute
- - `Spring.Data.Integration.Tests_Spring_database.sql`
- - `Spring.Data.Integration.Tests_Credits_database.sql`
- - `Spring.Data.Integration.Tests_Debits_database.sql`
- - `Spring.Data.Integration.Tests_CreditsAndDebits_database.sql`
- - Create a database named `quartz` and execute `tables_sqlServer_Below2016.sql` from [Quartz.NET](https://github.com/quartznet/quartznet/blob/main/database/tables/tables_sqlServer_Below2016.sql)
+- `./build.cmd Test` (`./build.sh Test`) runs the unit tests. They need nothing but the .NET SDK.
+- The SQL Server backed integration tests (`Spring.Data.Integration.Tests`, `Spring.Data.NHibernate5.Integration.Tests`,
+ `Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests` and `Spring.Scheduling.Quartz3.Integration.Tests`) provision their
+ own database with [Testcontainers for .NET](https://dotnet.testcontainers.org/). All you need is a running Docker daemon
+ (Linux containers):
+
+ ```
+ ./build.cmd IntegrationTest
+ ```
+
+ Each test assembly starts a pinned SQL Server 2022 container, creates the `springqa` login and the databases from
+ `build-support/create-integration-test-databases-and-users.sql` and the project's own `*_database.sql` script (the `quartz`
+ database gets the Quartz.NET tables), runs, and removes the container. There is no database to install, no hosts entry and no
+ script to run by hand. CI runs the same target on Linux (the `integration-linux` job).
+- To run against a SQL Server you already have, for example on a Windows machine without a Linux container engine, set
+ `SPRING_TEST_SQLSERVER` to an administrator's connection string. No container is started and the same scripts run against that
+ server (they drop and recreate the test databases and the `springqa` login, so do not point it at a server you care about).
+ The server must allow SQL Server (mixed-mode) authentication, because the tests log in as `springqa`. In PowerShell:
+
+ ```
+ $env:SPRING_TEST_SQLSERVER = "Server=localhost;Integrated Security=true;TrustServerCertificate=true"
+ ./build.cmd IntegrationTest
+ ```
+
+ `./build.cmd IntegrationTest` runs the net10.0 and net472 builds of a project one after the other when the variable is set.
+ When you call `dotnet test` yourself, pass `--max-parallel-test-modules 1`, because Microsoft.Testing.Platform otherwise runs
+ both builds at once and they would drop and recreate the same databases under each other:
+
+ ```
+ dotnet test --project test/Spring/Spring.Data.Integration.Tests/Spring.Data.Integration.Tests.csproj --max-parallel-test-modules 1
+ ```
+- The tests learn where the server is from these environment variables, which the test assembly sets once the scripts have run
+ (the XML contexts use them as `${...}` placeholders):
+
+ | Variable | Value |
+ |---|---|
+ | `SPRING_TEST_SQLSERVER_HOST` | `host,port` of the server |
+ | `SPRING_TEST_SQLSERVER_USER`, `SPRING_TEST_SQLSERVER_PASSWORD` | the `springqa` login (`springqa` / `springqa`) |
+ | `SPRING_TEST_SQLSERVER_CS_` | a complete connection string, as `springqa`, for each database the scripts created, with the name upper-cased: `..._CS_SPRING`, `..._CS_CREDITS`, `..._CS_DEBITS`, `..._CS_CREDITSANDDEBITS`, `..._CS_QUARTZ` |
+
+- Tests that need a distributed transaction (MSDTC) are `[Explicit]`: a SQL Server container cannot take part in one. Run them
+ by name against a Windows server with MSDTC enabled if you work on that code.
+- `./build.cmd Test --test-integration-data` also runs the four SQL Server projects as part of the `Test` target, and
+ `--test-full` runs every integration project, including the ones that need ActiveMQ, MSMQ or a SQLite file.
## Acknowledgements
diff --git a/build-support/build/Build.Test.cs b/build-support/build/Build.Test.cs
index 8ece3740e..ee19e7a2a 100644
--- a/build-support/build/Build.Test.cs
+++ b/build-support/build/Build.Test.cs
@@ -1,8 +1,10 @@
+using System.Collections.Generic;
using System.Linq;
using Fallout.Common;
using Fallout.Common.IO;
using Fallout.Common.Tooling;
using Fallout.Common.Tools.DotNet;
+using Fallout.Solutions;
using static Fallout.Common.Tools.DotNet.DotNetTasks;
public partial class Build
@@ -14,6 +16,24 @@ public partial class Build
AbsolutePath TestResultsDirectory => ArtifactsDirectory / "TestResults";
+ // The integration test projects that provision their own SQL Server with Testcontainers (test/Shared/SqlServerTestEnvironment.cs).
+ // They need a running Docker daemon, or a server named by the SPRING_TEST_SQLSERVER environment variable, and nothing else.
+ static readonly string[] SqlServerIntegrationTestProjects =
+ [
+ "Spring.Data.Integration.Tests",
+ "Spring.Data.NHibernate5.Integration.Tests",
+ "Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests",
+ "Spring.Scheduling.Quartz3.Integration.Tests",
+ ];
+
+ // Projects that target net472 only (no net10.0 build to run on Linux).
+ static readonly string[] WindowsOnlyIntegrationTestProjects =
+ [
+ "Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests",
+ ];
+
+ static bool IsSqlServerIntegrationTestProject(Project project) => SqlServerIntegrationTestProjects.Contains(project.Name);
+
Target Test => _ => _
.DependsOn(Restore)
.After(Compile)
@@ -23,45 +43,77 @@ public partial class Build
// The test projects only: Spring.Testing.NUnit and Spring.Testing.Microsoft are shipped libraries that
// merely contain ".Test", and a Microsoft.Testing.Platform run of a project without tests is an error.
.Where(x => x.Name.EndsWith(".Tests"))
- .Where(x => !x.Name.Contains(".Integration") || TestFull)
+ // Integration tests need infrastructure, so they run only when asked for: --test-full for all of them, or one
+ // flag for the projects behind it. (The four SQL Server projects also have the IntegrationTest target.)
+ .Where(x => !x.Name.Contains(".Integration")
+ || TestFull
+ || (TestIntegrationData && IsSqlServerIntegrationTestProject(x))
+ || (TestIntegrationNms && x.Name == "Spring.Messaging.Nms.Integration.Tests"))
.Where(x => !x.Name.Contains("Spring.Web.Conversation.NHibernate5.Tests") || TestFull)
- .Where(x => !x.Name.Contains("Spring.Data.Integration.Tests") || TestIntegrationData)
.Where(x => !x.Name.Contains("Spring.Messaging.Tests") || TestIntegrationMsMq || TestFull)
- .Where(x => !x.Name.Contains("Spring.Nms.Integration.Tests") || TestIntegrationNms || TestFull)
.Where(x => !x.Name.Contains("Spring.Testing.Microsoft.Test") || EnvironmentInfo.IsWin)
.Where(x => !x.Name.Contains("Spring.Web.Tests") || EnvironmentInfo.IsWin)
- .Where(x => !x.Name.Contains("Spring.Web.Mvc5.Tests") || EnvironmentInfo.IsWin);
+ .Where(x => !x.Name.Contains("Spring.Web.Mvc5.Tests") || EnvironmentInfo.IsWin)
+ .Where(x => !WindowsOnlyIntegrationTestProjects.Contains(x.Name) || EnvironmentInfo.IsWin);
+
+ RunTestProjects(testTargets);
+ });
+
+ // Runs the four SQL Server backed integration test projects. Each starts its own SQL Server container, so a Docker
+ // daemon must be running (or SPRING_TEST_SQLSERVER must name a server). Unlike CI-only targets this one is meant to be
+ // run locally: ./build.cmd IntegrationTest
+ Target IntegrationTest => _ => _
+ .DependsOn(Restore)
+ .After(Compile)
+ .Executes(() =>
+ {
+ var testTargets = GetActiveProjects()
+ .Where(IsSqlServerIntegrationTestProject)
+ .Where(x => !WindowsOnlyIntegrationTestProjects.Contains(x.Name) || EnvironmentInfo.IsWin);
+
+ RunTestProjects(testTargets);
+ });
+
+ void RunTestProjects(IEnumerable testTargets)
+ {
+ var failedProjects = new List();
+
+ // Microsoft.Testing.Platform runs the modules of a multi-targeted project (net10.0 and net472) at the same time. Against
+ // a server named by SPRING_TEST_SQLSERVER they would drop and recreate the same databases under each other, so they run
+ // one after the other then. With a container per module (the default) there is nothing to share.
+ var maxParallelModules = string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable("SPRING_TEST_SQLSERVER"))
+ ? System.Environment.ProcessorCount
+ : 1;
- var failedProjects = new System.Collections.Generic.List();
- foreach (var project in testTargets)
+ foreach (var project in testTargets)
+ {
+ // The repo runs tests on Microsoft.Testing.Platform (global.json), where the project goes in --project
+ // rather than as a positional argument, which DotNetTest's settings cannot express. Each run also
+ // writes a TRX file under artifacts/TestResults/, so a red CI log has something to open.
+ // The interpolated string has to go to DotNet() as is: its handler quotes each {hole}, a pre-built string
+ // would be passed as a single argument.
+ var results = TestResultsDirectory / project.Name;
+ try
{
- // The repo runs tests on Microsoft.Testing.Platform (global.json), where the project goes in --project
- // rather than as a positional argument, which DotNetTest's settings cannot express. Each run also
- // writes a TRX file under artifacts/TestResults/, so a red CI log has something to open.
- // The interpolated string has to go to DotNet() as is: its handler quotes each {hole}, a pre-built string
- // would be passed as a single argument.
- var results = TestResultsDirectory / project.Name;
- try
+ if (EnvironmentInfo.IsWin)
{
- if (EnvironmentInfo.IsWin)
- {
- DotNet($"test --project {project.Path} --configuration {Configuration} --no-restore --no-progress --report-trx --results-directory {results}");
- }
- else
- {
- DotNet($"test --project {project.Path} --configuration {Configuration} --no-restore --no-progress --report-trx --results-directory {results} --framework net10.0");
- }
+ DotNet($"test --project {project.Path} --configuration {Configuration} --no-restore --no-progress --report-trx --results-directory {results} --max-parallel-test-modules {maxParallelModules}");
}
- catch (ProcessException)
+ else
{
- // Keep going so one red project does not hide the state of the others; the target still fails below.
- failedProjects.Add(project.Name);
+ DotNet($"test --project {project.Path} --configuration {Configuration} --no-restore --no-progress --report-trx --results-directory {results} --framework net10.0");
}
}
-
- if (failedProjects.Count > 0)
+ catch (ProcessException)
{
- throw new System.Exception("Test projects failed: " + string.Join(", ", failedProjects));
+ // Keep going so one red project does not hide the state of the others; the target still fails below.
+ failedProjects.Add(project.Name);
}
- });
+ }
+
+ if (failedProjects.Count > 0)
+ {
+ throw new System.Exception("Test projects failed: " + string.Join(", ", failedProjects));
+ }
+ }
}
diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml
deleted file mode 100644
index 4615f83d0..000000000
--- a/docker-compose-ci.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-version: '2'
-
-services:
-
- sqlserver:
- image: mcr.microsoft.com/mssql/server:2019-latest
- environment:
- - MSSQL_PID=Express
- - ACCEPT_EULA=Y
- - SA_PASSWORD=MyPassword!
- ports:
- - 1433:1433
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 1bec6dfb6..000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-version: '2'
-
-services:
-
- sqlserver:
- image: mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04
- environment:
- - MSSQL_PID=Express
- - ACCEPT_EULA=Y
- - SA_PASSWORD=MyPassword!
- ports:
- - 1433:1433
diff --git a/test/Shared/SqlServerTestEnvironment.cs b/test/Shared/SqlServerTestEnvironment.cs
new file mode 100644
index 000000000..db863c925
--- /dev/null
+++ b/test/Shared/SqlServerTestEnvironment.cs
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2002-2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Data.SqlClient;
+using System.Text;
+
+using Testcontainers.MsSql;
+
+namespace Spring.IntegrationTests;
+
+///
+/// Provisions the SQL Server that the integration test assemblies run against, and publishes where it is.
+///
+///
+///
+/// This file is compiled into each SQL Server backed integration test project (a linked source file, not a project of
+/// its own), and each assembly's [SetUpFixture] calls and
+/// . Every assembly therefore gets its own server, so the assemblies can run in parallel
+/// without port clashes.
+///
+///
+/// By default a SQL Server container is started with Testcontainers, which needs only a running Docker daemon.
+/// When the environment variable is set, to an administrator's connection
+/// string, no container is started and the same scripts run against that server instead (for a developer without a Linux
+/// container engine).
+///
+///
+/// Once the scripts have run, the process environment variables below tell the tests where the server is. The Spring XML
+/// contexts read them through ${NAME} placeholders; C# code uses .
+///
+///
+/// SPRING_TEST_SQLSERVER_HOST: the server as host,port (the value of Data Source).
+/// SPRING_TEST_SQLSERVER_USER and SPRING_TEST_SQLSERVER_PASSWORD: the springqa login the scripts create.
+/// SPRING_TEST_SQLSERVER_CS_<DATABASE>: a complete connection string, as springqa, for each
+/// database the scripts created, with the name upper-cased, for example SPRING_TEST_SQLSERVER_CS_SPRING,
+/// SPRING_TEST_SQLSERVER_CS_CREDITSANDDEBITS and SPRING_TEST_SQLSERVER_CS_QUARTZ.
+///
+///
+internal static class SqlServerTestEnvironment
+{
+ ///
+ /// The image the container runs. Pinned, so that a run does not change with the registry's "latest".
+ ///
+ public const string Image = "mcr.microsoft.com/mssql/server:2022-CU27-ubuntu-22.04";
+
+ ///
+ /// When set to an administrator's connection string, the tests use that server instead of starting a container.
+ ///
+ public const string AdminConnectionStringVariable = "SPRING_TEST_SQLSERVER";
+
+ public const string HostVariable = "SPRING_TEST_SQLSERVER_HOST";
+
+ public const string UserVariable = "SPRING_TEST_SQLSERVER_USER";
+
+ public const string PasswordVariable = "SPRING_TEST_SQLSERVER_PASSWORD";
+
+ public const string ConnectionStringVariablePrefix = "SPRING_TEST_SQLSERVER_CS_";
+
+ ///
+ /// The login the scripts create and the tests connect as.
+ ///
+ public const string TestUser = "springqa";
+
+ public const string TestPassword = "springqa";
+
+ ///
+ /// The script, relative to the repository root, that creates the login and the databases every suite shares.
+ ///
+ public const string CommonScript = "build-support/create-integration-test-databases-and-users.sql";
+
+ private const string ContainerAdminPassword = "Spring!Adm1nP4ssw0rd";
+
+ private static readonly SemaphoreSlim s_lock = new(1, 1);
+ private static MsSqlContainer s_container;
+ private static bool s_initialized;
+
+ ///
+ /// Starts (or connects to) the server, runs and then , and
+ /// publishes the environment variables.
+ ///
+ /// Further scripts to run, in order, as paths relative to the repository root.
+ /// Text to substitute in every script, for scripts with placeholders.
+ public static async Task InitializeAsync(IEnumerable scripts = null, IReadOnlyDictionary replacements = null)
+ {
+ await s_lock.WaitAsync();
+ try
+ {
+ if (s_initialized)
+ {
+ return;
+ }
+
+ try
+ {
+ string adminConnectionString = Environment.GetEnvironmentVariable(AdminConnectionStringVariable);
+ if (string.IsNullOrWhiteSpace(adminConnectionString))
+ {
+ s_container = new MsSqlBuilder(Image)
+ .WithPassword(ContainerAdminPassword)
+ .Build();
+ await s_container.StartAsync();
+ adminConnectionString = s_container.GetConnectionString();
+ }
+
+ var admin = new SqlConnectionStringBuilder(adminConnectionString)
+ {
+ InitialCatalog = "master",
+ TrustServerCertificate = true,
+ };
+
+ foreach (string script in new[] { CommonScript }.Concat(scripts ?? []))
+ {
+ await RunScriptAsync(admin.ConnectionString, script, replacements);
+ }
+
+ await PublishAsync(admin);
+ s_initialized = true;
+ }
+ catch
+ {
+ // Report the failure of the setup, not one of the cleanup after it.
+ try
+ {
+ await DisposeContainerAsync();
+ }
+ catch (Exception)
+ {
+ // The container's Ryuk reaper removes it anyway.
+ }
+
+ throw;
+ }
+ }
+ finally
+ {
+ s_lock.Release();
+ }
+ }
+
+ ///
+ /// Stops the container, if one was started.
+ ///
+ public static async Task DisposeAsync()
+ {
+ await s_lock.WaitAsync();
+ try
+ {
+ s_initialized = false;
+ await DisposeContainerAsync();
+ }
+ finally
+ {
+ s_lock.Release();
+ }
+ }
+
+ ///
+ /// Gets the connection string, as the springqa login, for a database a script created.
+ ///
+ public static string GetConnectionString(string database)
+ {
+ string name = ConnectionStringVariablePrefix + database.ToUpperInvariant();
+ return Environment.GetEnvironmentVariable(name)
+ ?? throw new InvalidOperationException(
+ $"No connection string for database '{database}': '{name}' is not set. "
+ + "Run the test from an assembly whose [SetUpFixture] calls SqlServerTestEnvironment.InitializeAsync.");
+ }
+
+ ///
+ /// Gets the host,port of the server.
+ ///
+ public static string GetHost()
+ {
+ return Environment.GetEnvironmentVariable(HostVariable)
+ ?? throw new InvalidOperationException($"'{HostVariable}' is not set; SqlServerTestEnvironment has not been initialized.");
+ }
+
+ private static async Task PublishAsync(SqlConnectionStringBuilder admin)
+ {
+ var databases = new List();
+ using (var connection = new SqlConnection(admin.ConnectionString))
+ {
+ await connection.OpenAsync();
+ using SqlCommand command = connection.CreateCommand();
+ command.CommandText = "SELECT name FROM sys.databases WHERE database_id > 4 ORDER BY name";
+ using SqlDataReader reader = await command.ExecuteReaderAsync();
+ while (await reader.ReadAsync())
+ {
+ databases.Add(reader.GetString(0));
+ }
+ }
+
+ Environment.SetEnvironmentVariable(HostVariable, admin.DataSource);
+ Environment.SetEnvironmentVariable(UserVariable, TestUser);
+ Environment.SetEnvironmentVariable(PasswordVariable, TestPassword);
+
+ foreach (string database in databases)
+ {
+ var builder = new SqlConnectionStringBuilder
+ {
+ DataSource = admin.DataSource,
+ InitialCatalog = database,
+ UserID = TestUser,
+ Password = TestPassword,
+ PersistSecurityInfo = true,
+ TrustServerCertificate = true,
+ };
+ Environment.SetEnvironmentVariable(ConnectionStringVariablePrefix + database.ToUpperInvariant(), builder.ConnectionString);
+ }
+ }
+
+ private static async Task RunScriptAsync(string connectionString, string repositoryRelativePath, IReadOnlyDictionary replacements)
+ {
+ string path = ResolveRepositoryFile(repositoryRelativePath);
+
+ // The scripts are UTF-16 or UTF-8 (SSMS output); ReadAllText follows the byte order mark.
+ string text = File.ReadAllText(path);
+ if (replacements is not null)
+ {
+ foreach (KeyValuePair replacement in replacements)
+ {
+ text = text.Replace(replacement.Key, replacement.Value);
+ }
+ }
+
+ using var connection = new SqlConnection(connectionString);
+ await connection.OpenAsync();
+
+ // One connection for the whole script: the "USE [database]" statements the scripts begin with stay in effect for
+ // the batches that follow, which is what sqlcmd and SSMS do.
+ int batchNumber = 0;
+ foreach (string batch in SplitBatches(text))
+ {
+ batchNumber++;
+ try
+ {
+ using SqlCommand command = connection.CreateCommand();
+ command.CommandText = batch;
+ command.CommandTimeout = 120;
+ await command.ExecuteNonQueryAsync();
+ }
+ catch (SqlException e)
+ {
+ throw new InvalidOperationException(
+ $"Script '{repositoryRelativePath}' failed in batch {batchNumber}: {e.Message}{Environment.NewLine}{batch}", e);
+ }
+ }
+ }
+
+ ///
+ /// Splits a script into batches at the lines that consist of GO, which is a client command, not T-SQL.
+ ///
+ internal static IEnumerable SplitBatches(string script)
+ {
+ var batch = new StringBuilder();
+ foreach (string line in script.Split('\n'))
+ {
+ if (line.Trim().Equals("GO", StringComparison.OrdinalIgnoreCase))
+ {
+ if (batch.ToString().Trim().Length > 0)
+ {
+ yield return batch.ToString();
+ }
+
+ batch.Clear();
+ }
+ else
+ {
+ batch.Append(line).Append('\n');
+ }
+ }
+
+ if (batch.ToString().Trim().Length > 0)
+ {
+ yield return batch.ToString();
+ }
+ }
+
+ private static string ResolveRepositoryFile(string repositoryRelativePath)
+ {
+ string relativePath = repositoryRelativePath.Replace('/', Path.DirectorySeparatorChar);
+ DirectoryInfo current = new(AppContext.BaseDirectory);
+ while (current is not null)
+ {
+ string candidate = Path.Combine(current.FullName, relativePath);
+ if (File.Exists(candidate))
+ {
+ return candidate;
+ }
+
+ current = current.Parent;
+ }
+
+ throw new FileNotFoundException(
+ $"Could not find '{repositoryRelativePath}' in any directory above '{AppContext.BaseDirectory}'. "
+ + "The integration tests must run from a checkout of the repository.");
+ }
+
+ private static async Task DisposeContainerAsync()
+ {
+ MsSqlContainer container = s_container;
+ s_container = null;
+ if (container is not null)
+ {
+ await container.DisposeAsync();
+ }
+ }
+}
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/DTC1.1AppContext.xml b/test/Spring/Spring.Data.Integration.Tests/Data/DTC1.1AppContext.xml
index 87a22c5c7..3c3c0d4a0 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/DTC1.1AppContext.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/DTC1.1AppContext.xml
@@ -1,6 +1,14 @@
+
+
@@ -9,13 +17,13 @@
+ value="${SPRING_TEST_SQLSERVER_CS_CREDITS}" />
+ value="${SPRING_TEST_SQLSERVER_CS_DEBITS}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContext.xml b/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContext.xml
index 2a0b4b6d2..6e3e3f273 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContext.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContext.xml
@@ -4,6 +4,14 @@
xmlns:tx="http://www.springframework.net/tx"
xmlns:aop="http://www.springframework.net/aop">
+
+
+
+
+
+
+
+
@@ -11,17 +19,11 @@
-
+ connectionString="${SPRING_TEST_SQLSERVER_CS_DEBITS}" />
-
+ connectionString="${SPRING_TEST_SQLSERVER_CS_CREDITS}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContextNoInterfaces.xml b/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContextNoInterfaces.xml
index 131fc6db5..60a37e9ed 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContextNoInterfaces.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/DTCAppContextNoInterfaces.xml
@@ -2,15 +2,23 @@
+
+
+
+
+
+
+
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_CREDITS}" />
+ connectionString="${SPRING_TEST_SQLSERVER_CS_DEBITS}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs
index 02787333e..4d5fbd4a2 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs
@@ -47,9 +47,7 @@ public void TearDown()
}
[Test]
-#if NETCOREAPP
- [Ignore("Not supported on .NET Core")]
-#endif
+ [Explicit("Requires MSDTC: the two connections (Credits and Debits databases) enlist in one distributed transaction, which a SQL Server container cannot take part in")]
public void DeclarativeWithAttributes()
{
IAccountManager mgr = ctx["accountManager"] as IAccountManager;
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/DTCTestsNoInterfaces.cs b/test/Spring/Spring.Data.Integration.Tests/Data/DTCTestsNoInterfaces.cs
index 8de1fc162..cb3edd4b0 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/DTCTestsNoInterfaces.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/DTCTestsNoInterfaces.cs
@@ -42,9 +42,7 @@ public void TearDown()
}
[Test]
-#if NETCOREAPP
- [Ignore("Not supported on .NET Core")]
-#endif
+ [Explicit("Requires MSDTC: the two connections (Credits and Debits databases) enlist in one distributed transaction, which a SQL Server container cannot take part in")]
public void DeclarativeWithAttributes()
{
SimpleAccountManager mgr = ctx["accountManager"] as SimpleAccountManager;
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Generic/GenericAdoTemplateTests.xml b/test/Spring/Spring.Data.Integration.Tests/Data/Generic/GenericAdoTemplateTests.xml
index 725cb425f..5200998ab 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/Generic/GenericAdoTemplateTests.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/Generic/GenericAdoTemplateTests.xml
@@ -2,9 +2,17 @@
+
+
+
+
+
+
+
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/NativeAdoTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/NativeAdoTests.cs
index 4fa3a5529..bad8eac68 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/NativeAdoTests.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/NativeAdoTests.cs
@@ -19,6 +19,7 @@
using NUnit.Framework;
using Spring.Context;
using Spring.Context.Support;
+using Spring.IntegrationTests;
namespace Spring.Data;
@@ -40,7 +41,7 @@ public void SimpleUsage()
public void Helloworld()
{
string connString =
- @"Data Source=SPRINGQA;Initial Catalog=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False";
+ SqlServerTestEnvironment.GetConnectionString("Spring");
SqlConnection conn = new SqlConnection(connString);
conn.Open();
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/NestedTxScopeTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/NestedTxScopeTests.cs
index 340a8fbbe..1b5d8e401 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/NestedTxScopeTests.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/NestedTxScopeTests.cs
@@ -20,6 +20,7 @@
using NUnit.Framework;
using Spring.Data.Common;
using Spring.Data.Core;
+using Spring.IntegrationTests;
using Spring.Transaction;
using Spring.Transaction.Support;
@@ -36,7 +37,7 @@ public class NestedTxScopeTests
public void TxTemplate()
{
IDbProvider dbProvider = DbProviderFactory.GetDbProvider("System.Data.SqlClient");
- dbProvider.ConnectionString = @"Data Source=SPRINGQA;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa";
+ dbProvider.ConnectionString = SqlServerTestEnvironment.GetConnectionString("CreditsAndDebits");
//IPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager();
//IPlatformTransactionManager tm = new TxScopeTransactionManager();
IPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
@@ -84,7 +85,7 @@ private void Method1()
using (SqlConnection cn2005 = new SqlConnection())
{
cn2005.ConnectionString =
- @"Data Source=SPRINGQA;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa";
+ SqlServerTestEnvironment.GetConnectionString("CreditsAndDebits");
SqlCommand cmd = new SqlCommand(updateSql1, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
@@ -108,7 +109,7 @@ private void Method2()
System.Transactions.Transaction.Current.TransactionInformation.LocalIdentifier);
using (SqlConnection cn2005 = new SqlConnection())
{
- cn2005.ConnectionString = @"Data Source=SPRINGQA;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa";
+ cn2005.ConnectionString = SqlServerTestEnvironment.GetConnectionString("CreditsAndDebits");
SqlCommand cmd = new SqlCommand(updateSql2, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
@@ -144,7 +145,7 @@ private void InnerMethod()
using (SqlConnection cn2005 = new SqlConnection())
{
- cn2005.ConnectionString = @"Data Source=SPRINGQA;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa";
+ cn2005.ConnectionString = SqlServerTestEnvironment.GetConnectionString("CreditsAndDebits");
SqlCommand cmd = new SqlCommand(updateSql2, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Northwind/NativeAdoShipperDao.cs b/test/Spring/Spring.Data.Integration.Tests/Data/Northwind/NativeAdoShipperDao.cs
index 2c95b003c..c16c5fb31 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/Northwind/NativeAdoShipperDao.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/Northwind/NativeAdoShipperDao.cs
@@ -16,6 +16,7 @@
using System.Data;
using System.Data.SqlClient;
+using Spring.IntegrationTests;
namespace Spring.Data.Northwind;
@@ -24,7 +25,7 @@ public class NativeAdoShipperDao : IShipperDao
{
public Shipper Create(string name, string phone)
{
- string connectionString = "Data Source=SPRINGQA;Initial Catalog=Northwind;Persist Security Info=True;User ID=springqa;Password=springqa";
+ string connectionString = SqlServerTestEnvironment.GetConnectionString("Northwind");
int id = 0;
using (SqlConnection connection = new SqlConnection(connectionString))
{
@@ -55,7 +56,7 @@ public Shipper Create(string name, string phone)
public Shipper CreateShorter(string name, string phone)
{
- string connectionString = "Data Source=SPRINGQA;Initial Catalog=Northwind;Persist Security Info=True;User ID=springqa";
+ string connectionString = SqlServerTestEnvironment.GetConnectionString("Northwind");
int id = 0;
using (SqlConnection connection = new SqlConnection(connectionString))
{
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Objects/Generic/StoredProcedureTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/Objects/Generic/StoredProcedureTests.cs
index e91274a3e..96fb47cb5 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/Objects/Generic/StoredProcedureTests.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/Objects/Generic/StoredProcedureTests.cs
@@ -20,6 +20,7 @@
using NUnit.Framework;
using Spring.Data.Common;
using Spring.Data.Generic;
+using Spring.IntegrationTests;
using Spring.Objects;
namespace Spring.Data.Objects.Generic;
@@ -38,7 +39,7 @@ public void Setup()
{
_dbProvider = DbProviderFactory.GetDbProvider("System.Data.SqlClient");
_dbProvider.ConnectionString =
- @"Data Source=SPRINGQA;Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False";
+ SqlServerTestEnvironment.GetConnectionString("Spring");
IDbCommand command = _dbProvider.CreateCommand();
command.Connection = _dbProvider.CreateConnection();
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/SQLiteTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/SQLiteTests.cs
index 2316e5ba1..785cd9ddc 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/SQLiteTests.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/SQLiteTests.cs
@@ -21,6 +21,7 @@
using NUnit.Framework;
using Spring.Data.Common;
using Spring.Data.Generic;
+using Spring.IntegrationTests;
using Spring.Data.Objects;
namespace Spring.Data;
@@ -61,7 +62,7 @@ public void SqlServerTest()
IDbProvider dbProvider = DbProviderFactory.GetDbProvider("System.Data.SqlClient");
dbProvider.ConnectionString =
- @"Data Source=SPRINGQA;Initial Catalog=Spring;Persist Security Info=True;User ID=springqa;Password=springqa";
+ SqlServerTestEnvironment.GetConnectionString("Spring");
AdoTemplate adoTemplate = new AdoTemplate(dbProvider);
try
{
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_CreditsAndDebits_database.sql b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_CreditsAndDebits_database.sql
index 078ed39cb..09263f92f 100644
Binary files a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_CreditsAndDebits_database.sql and b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_CreditsAndDebits_database.sql differ
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Credits_database.sql b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Credits_database.sql
index a056f117c..94e367553 100644
Binary files a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Credits_database.sql and b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Credits_database.sql differ
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Debits_database.sql b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Debits_database.sql
index 7a8319012..f5c45afe9 100644
Binary files a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Debits_database.sql and b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Debits_database.sql differ
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Spring_database.sql b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Spring_database.sql
index ec8f916c9..14ff40512 100644
Binary files a/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Spring_database.sql and b/test/Spring/Spring.Data.Integration.Tests/Data/Spring.Data.Integration.Tests_Spring_database.sql differ
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevel.xml b/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevel.xml
index 70d60ceb8..f691d2ac9 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevel.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevel.xml
@@ -8,13 +8,14 @@
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs
index 19c5281f2..025cbbc3f 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs
@@ -19,6 +19,7 @@
using NUnit.Framework;
using Spring.Context;
using Spring.Context.Support;
+using Spring.IntegrationTests;
using Spring.Objects;
namespace Spring.Data;
@@ -71,7 +72,7 @@ public void Dowork2()
using (SqlConnection cn2005 = new SqlConnection())
{
cn2005.ConnectionString =
- @"Data Source=SPRINGQA;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa";
+ SqlServerTestEnvironment.GetConnectionString("CreditsAndDebits");
SqlCommand cmd = new SqlCommand(updateSql2, cn2005);
cn2005.Open();
Console.WriteLine("Isolation level = " + System.Transactions.Transaction.Current.IsolationLevel);
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/adoTemplateTests.xml b/test/Spring/Spring.Data.Integration.Tests/Data/adoTemplateTests.xml
index f6eb6e451..487d61f94 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/adoTemplateTests.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/adoTemplateTests.xml
@@ -7,13 +7,14 @@
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/autoDeclarativeServices.xml b/test/Spring/Spring.Data.Integration.Tests/Data/autoDeclarativeServices.xml
index b30297d1d..a8dde647b 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/autoDeclarativeServices.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/autoDeclarativeServices.xml
@@ -3,16 +3,24 @@
xmlns:db="http://www.springframework.net/database"
xmlns:tx="http://www.springframework.net/tx">
+
+
+
+
+
+
+
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/declarativeServices.xml b/test/Spring/Spring.Data.Integration.Tests/Data/declarativeServices.xml
index 9b0e15bc0..c06ad6c98 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/declarativeServices.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/declarativeServices.xml
@@ -6,13 +6,14 @@
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/nativeAdoTests.xml b/test/Spring/Spring.Data.Integration.Tests/Data/nativeAdoTests.xml
index 190d7c9b3..dcba03dad 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/nativeAdoTests.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/nativeAdoTests.xml
@@ -1,11 +1,19 @@
+
+
+
+
+
+
+
+
+ value="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
\ No newline at end of file
diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/templateTests.xml b/test/Spring/Spring.Data.Integration.Tests/Data/templateTests.xml
index 2285c03a8..50d0320de 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Data/templateTests.xml
+++ b/test/Spring/Spring.Data.Integration.Tests/Data/templateTests.xml
@@ -2,9 +2,17 @@
+
+
+
+
+
+
+
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.Integration.Tests/Spring.Data.Integration.Tests.csproj b/test/Spring/Spring.Data.Integration.Tests/Spring.Data.Integration.Tests.csproj
index d90c607d3..ef3acb75c 100644
--- a/test/Spring/Spring.Data.Integration.Tests/Spring.Data.Integration.Tests.csproj
+++ b/test/Spring/Spring.Data.Integration.Tests/Spring.Data.Integration.Tests.csproj
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/test/Spring/Spring.Data.Integration.Tests/TestAssemblySetup.cs b/test/Spring/Spring.Data.Integration.Tests/TestAssemblySetup.cs
new file mode 100644
index 000000000..f7db3c86a
--- /dev/null
+++ b/test/Spring/Spring.Data.Integration.Tests/TestAssemblySetup.cs
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using NUnit.Framework;
+using Spring.IntegrationTests;
+
+///
+/// Starts the SQL Server the tests of this assembly run against (see ), once,
+/// before the first of them.
+///
+[SetUpFixture]
+public class TestAssemblySetup
+{
+ private const string DataDirectory = "test/Spring/Spring.Data.Integration.Tests/Data/";
+
+ [OneTimeSetUp]
+ public async Task SetUp()
+ {
+ await SqlServerTestEnvironment.InitializeAsync(
+ [
+ DataDirectory + "Spring.Data.Integration.Tests_Spring_database.sql",
+ DataDirectory + "Spring.Data.Integration.Tests_Credits_database.sql",
+ DataDirectory + "Spring.Data.Integration.Tests_Debits_database.sql",
+ DataDirectory + "Spring.Data.Integration.Tests_CreditsAndDebits_database.sql",
+ ]);
+ }
+
+ [OneTimeTearDown]
+ public async Task TearDown()
+ {
+ await SqlServerTestEnvironment.DisposeAsync();
+ }
+}
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Dao.xml b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Dao.xml
index f0785a8a0..a88fdf2d1 100644
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Dao.xml
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Dao.xml
@@ -3,6 +3,14 @@
xmlns:db="http://www.springframework.net/database"
xmlns:tx="http://www.springframework.net/tx">
+
+
+
+
+
+
+
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING};Max Pool Size=10;" />
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.cs b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.cs
index cdef49d64..b9d952e08 100644
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.cs
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.cs
@@ -27,7 +27,6 @@ namespace Spring.Data.NHibernate
///
/// Mark Pollack (.NET)
[TestFixture]
- [Ignore("Trouble running on Appveyor")]
public class MultipleDbTests
{
private IApplicationContext ctx;
@@ -53,8 +52,8 @@ public void TearDown()
ctx?.Dispose();
}
- // TODO: this test fails - check out why
[Test]
+ [Explicit("Requires MSDTC: the two session factories open two connections that enlist in one TransactionScope, which promotes it to a distributed transaction")]
public void MultipleDBAccess()
{
float transferAmount = 114;
@@ -67,6 +66,7 @@ public void MultipleDBAccess()
}
[Test]
+ [Explicit("Requires MSDTC: it runs MultipleDBAccess, whose two session factories enlist two connections in one TransactionScope")]
public void MultipleDBAccessUsingMultipleSessionScopes()
{
SessionScope scope1 = new SessionScope((ISessionFactory) ctx["SessionFactory1"], false);
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.xml b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.xml
index 4015da6fa..9294a4c8b 100644
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.xml
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/MultipleDbTests.xml
@@ -2,13 +2,21 @@
+
+
+
+
+
+
+
+
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/NHDAOTests.xml b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/NHDAOTests.xml
index 500071569..bebea3eb6 100644
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/NHDAOTests.xml
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/NHDAOTests.xml
@@ -2,15 +2,17 @@
-
+
+
+
+
+
+
+
-
+ provider="System.Data.SqlClient"
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Spring.Data.NHibernate5.Integration.Tests_database.sql b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Spring.Data.NHibernate5.Integration.Tests_database.sql
new file mode 100644
index 000000000..6be8c7d52
--- /dev/null
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Spring.Data.NHibernate5.Integration.Tests_database.sql
@@ -0,0 +1,41 @@
+-- The tables and the second login that Spring.Data.NHibernate5.Integration.Tests need in the Spring database.
+-- The Spring database and the springqa login come from build-support/create-integration-test-databases-and-users.sql.
+
+IF EXISTS (SELECT * FROM sys.server_principals WHERE name = N'springqa2')
+ DROP LOGIN [springqa2]
+GO
+
+CREATE LOGIN [springqa2] WITH PASSWORD=N'springqa2', DEFAULT_DATABASE=[Spring], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION = OFF, CHECK_POLICY = OFF
+GO
+
+USE [Spring]
+GO
+
+CREATE USER [springqa2] FOR LOGIN [springqa2] WITH DEFAULT_SCHEMA=[dbo]
+EXEC sp_addrolemember 'db_owner', 'springqa2'
+GO
+
+CREATE TABLE [dbo].[Credits]
+(
+ [CreditID] [int] IDENTITY(1,1) NOT NULL,
+ [CreditAmount] [float] NOT NULL,
+ CONSTRAINT [PK_CreditID] PRIMARY KEY CLUSTERED ([CreditID] ASC)
+)
+GO
+
+CREATE TABLE [dbo].[Debits]
+(
+ [DebitID] [int] IDENTITY(1,1) NOT NULL,
+ [DebitAmount] [float] NOT NULL,
+ CONSTRAINT [PK_DebitID] PRIMARY KEY CLUSTERED ([DebitID] ASC)
+)
+GO
+
+CREATE TABLE [dbo].[TestObjects]
+(
+ [TestObjectNo] [int] IDENTITY(1,1) NOT NULL,
+ [Age] [int] NOT NULL,
+ [Name] [nvarchar](1024) NULL,
+ CONSTRAINT [TestObjectNo] PRIMARY KEY CLUSTERED ([TestObjectNo] ASC)
+)
+GO
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/creditdebit.sql b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/creditdebit.sql
deleted file mode 100644
index b0a036601..000000000
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/creditdebit.sql
+++ /dev/null
@@ -1,51 +0,0 @@
-CREATE TABLE [Credits]
-(
- [
- CreditID] [
- int]
- IDENTITY
- NOT
- NULL, [
- CreditAmount] [
- float]
- NOT
- NULL,
- CONSTRAINT [
- PK_CreditID]
- PRIMARY
- KEY
- CLUSTERED
- (
-[
- CreditID]
- ASC
-) ON [PRIMARY]
- )
- ON [PRIMARY]
-;
-
-CREATE TABLE [Debits]
-(
- [
- DebitID] [
- int]
- IDENTITY
- NOT
- NULL, [
- DebitAmount] [
- float]
- NOT
- NULL,
- CONSTRAINT [
- PK_DebitID]
- PRIMARY
- KEY
- CLUSTERED
- (
-[
- DebitID]
- ASC
-) ON [PRIMARY]
- )
- ON [PRIMARY]
-;
\ No newline at end of file
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/dbProviderTemplateTests.xml b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/dbProviderTemplateTests.xml
index 9025c80a8..f391f5c7d 100644
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/dbProviderTemplateTests.xml
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/dbProviderTemplateTests.xml
@@ -2,11 +2,13 @@
-
+
+
+
+
+
+
+
@@ -16,20 +18,19 @@
+ connectionString="Data Source=${SPRING_TEST_SQLSERVER_HOST};Database=Spring;Trusted_Connection=False;TrustServerCertificate=True" />
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
- assembly://Spring.Data.NHibernate5.Integration.Tests/Spring.Data.NHibernate/TestObject.hbm.xml
-
+ assembly://Spring.Data.NHibernate5.Integration.Tests/Spring.Data.NHibernate/TestObject.hbm.xml
-
-
+ connectionString="${SPRING_TEST_SQLSERVER_CS_SPRING}" />
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/testObject.sql b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/testObject.sql
deleted file mode 100644
index 7cc24eff6..000000000
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/testObject.sql
+++ /dev/null
@@ -1,24 +0,0 @@
-CREATE TABLE [TestObjects]
-(
- [
- TestObjectNo]
- int
- IDENTITY
- NOT
- NULL, [
- Age]
- int
- NOT
- NULL, [
- Name]
- nvarchar
-(
- 1024
-),
- CONSTRAINT [TestObjectNo] PRIMARY KEY CLUSTERED
-(
-[
- TestObjectNo]
- ASC
-)
- );
\ No newline at end of file
diff --git a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/txScopeBugTests.xml b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/txScopeBugTests.xml
index d94e25f0d..4cedb2712 100644
--- a/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/txScopeBugTests.xml
+++ b/test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/txScopeBugTests.xml
@@ -3,6 +3,14 @@
xmlns:db="http://www.springframework.net/database"
xmlns:tx="http://www.springframework.net/tx">
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/Spring.Scheduling.Quartz3.Integration.Tests_database.sql b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/Spring.Scheduling.Quartz3.Integration.Tests_database.sql
new file mode 100644
index 000000000..80ce04a0f
--- /dev/null
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/Spring.Scheduling.Quartz3.Integration.Tests_database.sql
@@ -0,0 +1,22 @@
+-- The quartz database that Spring.Scheduling.Quartz3.Integration.Tests use, owned by the springqa login.
+-- tables_sqlServer.sql (vendored from Quartz.NET) then creates the Quartz tables in it.
+-- The springqa login comes from build-support/create-integration-test-databases-and-users.sql.
+
+-- drop the quartz DB if it exists (the script is run against a server that may already have been used)
+IF EXISTS (SELECT name FROM sys.databases WHERE name = N'quartz')
+BEGIN
+ ALTER DATABASE [quartz] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
+
+ DROP DATABASE [quartz]
+END
+GO
+
+CREATE DATABASE [quartz]
+GO
+
+USE [quartz]
+GO
+
+CREATE USER [springqa] FOR LOGIN [springqa] WITH DEFAULT_SCHEMA=[dbo]
+EXEC sp_addrolemember 'db_owner', 'springqa'
+GO
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/tables_sqlServer.sql b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/tables_sqlServer.sql
new file mode 100644
index 000000000..c3eb03cb4
--- /dev/null
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/tables_sqlServer.sql
@@ -0,0 +1,381 @@
+-- Vendored from the Quartz.NET repository, tag v3.22.9 (the Quartz version in Directory.Packages.props):
+-- https://github.com/quartznet/quartznet/blob/v3.22.9/database/tables/tables_sqlServer.sql
+-- Copyright Marko Lahma and the Quartz.NET contributors. Licensed under the Apache License, Version 2.0:
+-- http://www.apache.org/licenses/LICENSE-2.0
+-- Unmodified below this header, except that the test environment replaces the placeholder [enter_db_name_here]
+-- with [quartz] when it runs the script (see TestAssemblySetup.cs).
+--
+-- this script is for SQL Server and Azure SQL
+
+-- This initializes the database to pristine for Quartz, by first removing any existing Quartz tables
+-- and then recreating them from scratch.
+-- Should you only require it to create the tables, set @DropDb to 0.
+
+USE [enter_db_name_here];
+GO
+
+DECLARE @DropDb BIT = 1; -- Set this to 0 to skip DROP statements, 1 to include them
+
+IF @DropDb = 1
+BEGIN
+ -- drop indexes if they exist and rebuild if current ones
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_J] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_JG] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_C] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_G] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_G_J] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_STATE] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_N_STATE] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_N_G_STATE] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_NEXT_FIRE_TIME] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_ST] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_MISFIRE] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_ST_MISFIRE] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_ST_MISFIRE_GRP] ON [dbo].[QRTZ_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_TRIG_INST_NAME] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_INST_JOB_REQ_RCVRY] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_J_G] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_JG] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_T_G] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_TG] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_G_J] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+ DROP INDEX IF EXISTS [IDX_QRTZ_FT_G_T] ON [dbo].[QRTZ_FIRED_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS]', N'F') IS NOT NULL
+ ALTER TABLE [dbo].[QRTZ_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS];
+
+ IF OBJECT_ID(N'[dbo].[FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS]', N'F') IS NOT NULL
+ ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS]', N'F') IS NOT NULL
+ ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS]', N'F') IS NOT NULL
+ ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS];
+
+ IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_QRTZ_JOB_LISTENERS_QRTZ_JOB_DETAILS]') AND parent_object_id = OBJECT_ID(N'[dbo].[QRTZ_JOB_LISTENERS]'))
+ ALTER TABLE [dbo].[QRTZ_JOB_LISTENERS] DROP CONSTRAINT [FK_QRTZ_JOB_LISTENERS_QRTZ_JOB_DETAILS];
+
+ IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGER_LISTENERS_QRTZ_TRIGGERS]') AND parent_object_id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGER_LISTENERS]'))
+ ALTER TABLE [dbo].[QRTZ_TRIGGER_LISTENERS] DROP CONSTRAINT [FK_QRTZ_TRIGGER_LISTENERS_QRTZ_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_CALENDARS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_CALENDARS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_CRON_TRIGGERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_CRON_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_BLOB_TRIGGERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_BLOB_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_FIRED_TRIGGERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_FIRED_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_PAUSED_TRIGGER_GRPS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_JOB_LISTENERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_JOB_LISTENERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_SCHEDULER_STATE]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_SCHEDULER_STATE];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_LOCKS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_LOCKS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_TRIGGER_LISTENERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_TRIGGER_LISTENERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_JOB_DETAILS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_JOB_DETAILS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_SIMPLE_TRIGGERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_SIMPROP_TRIGGERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS];
+
+ IF OBJECT_ID(N'[dbo].[QRTZ_TRIGGERS]', N'U') IS NOT NULL
+ DROP TABLE [dbo].[QRTZ_TRIGGERS];
+END
+
+CREATE TABLE [dbo].[QRTZ_CALENDARS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [CALENDAR_NAME] nvarchar(200) NOT NULL,
+ [CALENDAR] varbinary(max) NOT NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_CRON_TRIGGERS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [TRIGGER_NAME] nvarchar(150) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL,
+ [CRON_EXPRESSION] nvarchar(120) NOT NULL,
+ [TIME_ZONE_ID] nvarchar(80)
+);
+
+CREATE TABLE [dbo].[QRTZ_FIRED_TRIGGERS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [ENTRY_ID] nvarchar(140) NOT NULL,
+ [TRIGGER_NAME] nvarchar(150) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL,
+ [INSTANCE_NAME] nvarchar(200) NOT NULL,
+ [FIRED_TIME] bigint NOT NULL,
+ [SCHED_TIME] bigint NOT NULL,
+ [PRIORITY] int NOT NULL,
+ [STATE] nvarchar(16) NOT NULL,
+ [JOB_NAME] nvarchar(150) NULL,
+ [JOB_GROUP] nvarchar(150) NULL,
+ [IS_NONCONCURRENT] bit NULL,
+ [REQUESTS_RECOVERY] bit NULL,
+ [EXECUTION_GROUP] nvarchar(200) NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_SCHEDULER_STATE] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [INSTANCE_NAME] nvarchar(200) NOT NULL,
+ [LAST_CHECKIN_TIME] bigint NOT NULL,
+ [CHECKIN_INTERVAL] bigint NOT NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_LOCKS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [LOCK_NAME] nvarchar(40) NOT NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_JOB_DETAILS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [JOB_NAME] nvarchar(150) NOT NULL,
+ [JOB_GROUP] nvarchar(150) NOT NULL,
+ [DESCRIPTION] nvarchar(250) NULL,
+ [JOB_CLASS_NAME] nvarchar(250) NOT NULL,
+ [IS_DURABLE] bit NOT NULL,
+ [IS_NONCONCURRENT] bit NOT NULL,
+ [IS_UPDATE_DATA] bit NOT NULL,
+ [REQUESTS_RECOVERY] bit NOT NULL,
+ [JOB_DATA] varbinary(max) NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [TRIGGER_NAME] nvarchar(150) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL,
+ [REPEAT_COUNT] int NOT NULL,
+ [REPEAT_INTERVAL] bigint NOT NULL,
+ [TIMES_TRIGGERED] int NOT NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [TRIGGER_NAME] nvarchar(150) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL,
+ [STR_PROP_1] nvarchar(512) NULL,
+ [STR_PROP_2] nvarchar(512) NULL,
+ [STR_PROP_3] nvarchar(512) NULL,
+ [INT_PROP_1] int NULL,
+ [INT_PROP_2] int NULL,
+ [LONG_PROP_1] bigint NULL,
+ [LONG_PROP_2] bigint NULL,
+ [DEC_PROP_1] numeric(13,4) NULL,
+ [DEC_PROP_2] numeric(13,4) NULL,
+ [BOOL_PROP_1] bit NULL,
+ [BOOL_PROP_2] bit NULL,
+ [TIME_ZONE_ID] nvarchar(80) NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_BLOB_TRIGGERS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [TRIGGER_NAME] nvarchar(150) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL,
+ [BLOB_DATA] varbinary(max) NULL
+);
+
+CREATE TABLE [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME] nvarchar(120) NOT NULL,
+ [TRIGGER_NAME] nvarchar(150) NOT NULL,
+ [TRIGGER_GROUP] nvarchar(150) NOT NULL,
+ [JOB_NAME] nvarchar(150) NOT NULL,
+ [JOB_GROUP] nvarchar(150) NOT NULL,
+ [DESCRIPTION] nvarchar(250) NULL,
+ [NEXT_FIRE_TIME] bigint NULL,
+ [PREV_FIRE_TIME] bigint NULL,
+ [PRIORITY] int NULL,
+ [TRIGGER_STATE] nvarchar(16) NOT NULL,
+ [TRIGGER_TYPE] nvarchar(8) NOT NULL,
+ [START_TIME] bigint NOT NULL,
+ [END_TIME] bigint NULL,
+ [CALENDAR_NAME] nvarchar(200) NULL,
+ [MISFIRE_INSTR] int NULL,
+ [MISFIRE_ORIG_FIRE_TIME] bigint NULL,
+ [EXECUTION_GROUP] nvarchar(200) NULL,
+ [PREFERRED_NODE] nvarchar(200) NULL,
+ [PREFERRED_NODE_AUTO] bit NOT NULL DEFAULT 0,
+ [JOB_DATA] varbinary(max) NULL
+);
+GO
+
+ALTER TABLE [dbo].[QRTZ_CALENDARS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_CALENDARS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [CALENDAR_NAME]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_CRON_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_FIRED_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [ENTRY_ID]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_PAUSED_TRIGGER_GRPS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_SCHEDULER_STATE] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [INSTANCE_NAME]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_LOCKS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_LOCKS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [LOCK_NAME]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_JOB_DETAILS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [JOB_NAME],
+ [JOB_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_SIMPLE_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_SIMPROP_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_BLOB_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_BLOB_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ );
+GO
+
+ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) REFERENCES [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) ON DELETE CASCADE;
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) REFERENCES [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) ON DELETE CASCADE;
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) REFERENCES [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) ON DELETE CASCADE;
+GO
+
+ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [JOB_NAME],
+ [JOB_GROUP]
+ ) REFERENCES [dbo].[QRTZ_JOB_DETAILS] (
+ [SCHED_NAME],
+ [JOB_NAME],
+ [JOB_GROUP]
+ );
+GO
+
+-- Create indexes
+CREATE INDEX [IDX_QRTZ_T_G_J] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, JOB_GROUP, JOB_NAME);
+CREATE INDEX [IDX_QRTZ_T_C] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, CALENDAR_NAME);
+
+CREATE INDEX [IDX_QRTZ_T_N_G_STATE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE);
+CREATE INDEX [IDX_QRTZ_T_N_STATE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE);
+CREATE INDEX [IDX_QRTZ_T_NEXT_FIRE_TIME] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, NEXT_FIRE_TIME);
+CREATE INDEX [IDX_QRTZ_T_NFT_ST] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME);
+CREATE INDEX [IDX_QRTZ_T_NFT_ST_MISFIRE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE);
+CREATE INDEX [IDX_QRTZ_T_NFT_ST_MISFIRE_GRP] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE);
+
+CREATE INDEX [IDX_QRTZ_FT_INST_JOB_REQ_RCVRY] ON [dbo].[QRTZ_FIRED_TRIGGERS](SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY);
+CREATE INDEX [IDX_QRTZ_FT_G_J] ON [dbo].[QRTZ_FIRED_TRIGGERS](SCHED_NAME, JOB_GROUP, JOB_NAME);
+CREATE INDEX [IDX_QRTZ_FT_G_T] ON [dbo].[QRTZ_FIRED_TRIGGERS](SCHED_NAME, TRIGGER_GROUP, TRIGGER_NAME);
+GO
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest-core.xml b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest-core.xml
index 52dd9ba7d..948070b6e 100644
--- a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest-core.xml
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest-core.xml
@@ -8,12 +8,11 @@
-
-
+
-
+
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.cs b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.cs
index 058bf1f9d..8eca082cf 100644
--- a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.cs
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.cs
@@ -44,10 +44,9 @@ public void TearDown()
}
[Test]
- [Explicit("Appveyor problems")]
public void TestLocalDataSourceJobStore()
{
- // sleep 20 seconds
- Thread.Sleep(20000);
+ // The scheduler starts after 5 seconds and fires the trigger 2 seconds after that, against the ADO job store.
+ Assert.That(TestJob.Executed.Wait(TimeSpan.FromSeconds(30)), Is.True, "the job was not executed by the scheduler");
}
}
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.xml b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.xml
index db5a80057..a5877596f 100644
--- a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.xml
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/LocalDataSourceJobStoreTest.xml
@@ -9,8 +9,7 @@
-
-
+
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/TestJob.cs b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/TestJob.cs
index 3896b33af..233c40da8 100644
--- a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/TestJob.cs
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Scheduling/Quartz/TestJob.cs
@@ -4,9 +4,15 @@ namespace Spring.Scheduling.Quartz.Integration.Tests;
public class TestJob : IJob
{
+ ///
+ /// Set when the scheduler has run the job once.
+ ///
+ public static readonly ManualResetEventSlim Executed = new(false);
+
public Task Execute(IJobExecutionContext context)
{
Console.WriteLine("Executing Execute!");
+ Executed.Set();
return Task.FromResult(true);
}
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Spring.Scheduling.Quartz3.Integration.Tests.csproj b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Spring.Scheduling.Quartz3.Integration.Tests.csproj
index f19b6e4d0..19138656b 100644
--- a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Spring.Scheduling.Quartz3.Integration.Tests.csproj
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Spring.Scheduling.Quartz3.Integration.Tests.csproj
@@ -15,6 +15,10 @@
+
+
+
+
diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/TestAssemblySetup.cs b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/TestAssemblySetup.cs
new file mode 100644
index 000000000..6e0d32c64
--- /dev/null
+++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/TestAssemblySetup.cs
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using NUnit.Framework;
+using Spring.IntegrationTests;
+
+///
+/// Starts the SQL Server the tests of this assembly run against (see ), once,
+/// before the first of them, with a quartz database that holds the Quartz.NET tables.
+///
+[SetUpFixture]
+public class TestAssemblySetup
+{
+ private const string DataDirectory = "test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/";
+
+ [OneTimeSetUp]
+ public async Task SetUp()
+ {
+ await SqlServerTestEnvironment.InitializeAsync(
+ [
+ DataDirectory + "Spring.Scheduling.Quartz3.Integration.Tests_database.sql",
+ DataDirectory + "tables_sqlServer.sql",
+ ],
+ new Dictionary { ["[enter_db_name_here]"] = "[quartz]" });
+ }
+
+ [OneTimeTearDown]
+ public async Task TearDown()
+ {
+ await SqlServerTestEnvironment.DisposeAsync();
+ }
+}