From 2f8f97b9d6e65f0ba6d0247a862fe24d4d0f5a16 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 4 Nov 2026 00:09:32 +0200 Subject: [PATCH 1/7] Spring.Data.Integration.Tests: provision SQL Server with Testcontainers Adds the shared SqlServerTestEnvironment (a linked source file) that starts a pinned SQL Server 2022 container, or uses the server named by SPRING_TEST_SQLSERVER, runs the database scripts and publishes SPRING_TEST_SQLSERVER_* environment variables. Spring.Data.Integration.Tests reads its connection strings from them instead of the hard-coded SPRINGQA host. The _database.sql scripts were mangled by a formatter (identifiers split across lines) and are rewritten. The two MSDTC tests are Explicit. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LGkLQuAuUjvRjrU1AcTsmk Signed-off-by: Marko Lahma --- Directory.Packages.props | 1 + test/Shared/SqlServerTestEnvironment.cs | 312 ++++++++++++++++++ .../Data/DTC1.1AppContext.xml | 12 +- .../Data/DTCAppContext.xml | 18 +- .../Data/DTCAppContextNoInterfaces.xml | 12 +- .../Data/DTCTests.cs | 4 +- .../Data/DTCTestsNoInterfaces.cs | 4 +- .../Data/Generic/GenericAdoTemplateTests.xml | 10 +- .../Data/NativeAdoTests.cs | 3 +- .../Data/NestedTxScopeTests.cs | 9 +- .../Data/Northwind/NativeAdoShipperDao.cs | 5 +- .../Objects/Generic/StoredProcedureTests.cs | 3 +- .../Data/SQLiteTests.cs | 3 +- ...ration.Tests_CreditsAndDebits_database.sql | Bin 2248 -> 886 bytes ...ata.Integration.Tests_Credits_database.sql | Bin 1120 -> 506 bytes ...Data.Integration.Tests_Debits_database.sql | Bin 1106 -> 494 bytes ...Data.Integration.Tests_Spring_database.sql | Bin 9132 -> 4150 bytes .../Data/TestTxIsolationLevel.xml | 3 +- .../Data/TestTxIsolationLevelTests.cs | 3 +- .../Data/adoTemplateTests.xml | 3 +- .../Data/autoDeclarativeServices.xml | 12 +- .../Data/declarativeServices.xml | 3 +- .../Data/nativeAdoTests.xml | 10 +- .../Data/templateTests.xml | 10 +- .../Spring.Data.Integration.Tests.csproj | 4 + .../TestAssemblySetup.cs | 46 +++ 26 files changed, 454 insertions(+), 36 deletions(-) create mode 100644 test/Shared/SqlServerTestEnvironment.cs create mode 100644 test/Spring/Spring.Data.Integration.Tests/TestAssemblySetup.cs 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/test/Shared/SqlServerTestEnvironment.cs b/test/Shared/SqlServerTestEnvironment.cs new file mode 100644 index 000000000..3b2203479 --- /dev/null +++ b/test/Shared/SqlServerTestEnvironment.cs @@ -0,0 +1,312 @@ +/* + * 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 + { + await DisposeContainerAsync(); + 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 078ed39cb747581171e9ce6f867ca4aa063ecf50..1848c6f2728713e444d272dd1c75cb5d390fd20b 100644 GIT binary patch delta 262 zcmX>h_>GP8|G!X%U&6GWM1y2mQ z#0Rsv?(X!PZ)d)p&RzbPs8gkfdY}W9I@gJgb*c+}Q=u=chggoTEK|pAtMz%HN72Zz z9_b_K_jqqAV~ll(?Gv^P_G8v(n(8s@l+~~{I(*ML>njWym_z0h=gPINfu3l?pXoMP)mx!dV09z#&!AlY3jWl>C>&3pp-lv}#M9^2x0$9m`Q z(lc_cB_6r&R$*;CZ<~9SQe1p=TMEI)A9XMSrI=VGSPweQergi^JuYaDxe zjp8@6-UC-_7wm|PGEkMzB^p}|o9d?jFv@nZlCfe14CP#deNGzd6{oBaPXVXZK*L<= zE!G$OS}I`+6|HLRuB|p~Rm0PHm9-v^<+%&Xgm+d8sc%i?nNAkDWjz5oo9C8d3$NN% z`yfX6*u2ABg11`k(T&FxV=aSi=f4fTI~8( zan1evo)M}hbdKN?at-^tcalFb-nFm%s+94jUw!&L#Q1-sx5N=Gv%AFSgv{^XUv9F7 Qo3)zw-(mWH+vRkapC-l^F#rGn 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 a056f117c8880f3123f69adffcd509207ace827b..c2e5f153a5ec84969af267ca532da46b8ef1c9ae 100644 GIT binary patch delta 177 zcmaFB@r#-B|G!X%U72e3zUfl^74Ru5E~@s%HYQk0%V0SL;`giGUzZEPG)2hpDe}X dH93tbO-}(RirpkfpsSo2G=VmPFwj*%5&(cBA6@_e literal 1120 zcmb7@TT8=05QWdP;D5+t6|onp4}uRdjT*E`ZCa|7QoKUN8(ROq`pwjo1WHAgWOpZL z&di*de18p&Gn&)TThhvUe|VM1z2zN2>KT6fdbY**Vvx01#sPQ zeuZOq1+SrP<#@jn?G27V9sK8b8(UKi)zwnl-wp09x9;(^t$lyDv6OSFd!AO!fG?p^ z_!CXphdO8FSTk-`Yf)8!z5tWq7n8*luMVe_d!l2WSp5~g5!RSE=4I7~IwFdl*H>@x zYo0}+!?RuLiG3s2D+~ddud!q2YN)Q7w$=1^T@CgD?;XB2wd?OaJmrdNUZr)?<4dR% z{zM~eUu{;7H{)b|7CjZ{b1)fkF;$F+YOzZ>CpzMZ)m;!9;f={-QPz8?L$cU@S?`v8 z&1)!B7*{O*Ht~?K+ zp1IG@-~86)J$F6vTeCWsGexd_j!s>aN?cvz=;##1=ezEJOLPXdOGg2y*lmQyHm|0- z>E9}KyIje#iXjZqOoIWLdO9J?8uf&5+6<)FSQmH?crqs82o-H=?XGP$Y?r{3`;=u~ zkM(&5OP_Bx3)R?!$upg-a_hPeay09$iVb~gGYvp=i80&8M&NChr|8scDr2pKW9MIl WUZ2f@{R`6@C@Wo^tpA~lzv>6-x{81R 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 ec8f916c9906c50459bbff26371a816b8af6de94..be0085a2617a138ce97cb0b7649afd209f978af4 100644 GIT binary patch literal 4150 zcmcgvYmd@E6uqDKSM(zqVqBw%UrZ>aAaR!>kKGtz%k;;Z(t>ms*AP-8Jhkx+Wl*utp3ui>EquDz*C|K`*dJn@`aN8IX~GumK`ghj zfbGxV&6V899li-XTEM53@~H-Ez4TL-=}HVKViC&}yJzwp8s>OclXH13b*w#?C%9(4 z8p#K>UWW|Vddhp=HBR;1nQdrY!kPr@H_$&#PyB^nOPu*t-Ff1x^enfY3&bJ z@NglIuw)9qwne~g)ewHsj|9;p_Qtz7{l#b)pWPZl#46r%ccAz<1S^8E(WYD(U z9syUzJ_6sr;rR~FE`Gb@dMZPR3Nt-X?AxEsK5B)DvDLRZ1MxP z7FET0lfS#hd6Oz*_ak3(-`E+<^PcioIinvS1VVJYmvImg0 zQ$&E1M84~qiaAj%oXHQ|r;v|v<=z<>IZO9$=HsN$LtVARc2UkGwkv1rbUt^9VT4-F znm<;xUrz*fw$P~Q{^qI^MV}jMJH0}+GhIGABQ(&9SXDiRVUl(fM#au9>}{OP2FfSh z=_2fKS+fHErsi^j(b_qwT;Z8rE`%<2H)|*5j2-N0x}WWe(-2_|&v9mAd zIb-dr!cRw2W7LE{9o79UUc`!1VxaJH*}}eP(czQ9%xY;XW?W*;rD2tc&$Yb8X`CCZ z9#7sVRIOgCId5NaQeno>qVnv>x5Y`2?_6^o-olT-BQ0^E1n@e5{_r6N^(7FSg@D z77-8g|1t9JU#RFDjDSmW5JLG17OZCsQ^ojSag2EP)%E}2$w;y0H<0&c=FxoQgqdgH TDeyif&s@yfRI^UONyEp#E6`o` literal 9132 zcmeHM+iu%N5S`}&{RcieK@rYxsiABO2RbE|7enZx0~Lx64?5)A2NS z$a}cv?W=vG^=^c87qIgjSXcrHedu-#J)QP{OFsp7 zQ?k3z_7>J$$R}t$gO$wk^{idAmJw`3o5VmG{mLvgUA@uM%zi~Oy70v5XY;q!k48Z7 z5Ev%f>2ViwCO^Y_(nxe3e+Jr~ya4?W$caGN)q5Krz$iqYpm%gURE5}0%XN0<&YMTH zAg8^?!a3#}%TM^a2hHf2^z1(FUHKKG&+s<@?w$khj79WEdOV+ckTcTQF;K0z;unxI z!8iJA$lHUr6W`ST67naYyj-s^q-|*|8pt^)hT!4-6!$jh8KE3?Uv=_nxq=gwpGW;b zS4}%G!nwWh?d|-=>{N}~Vx@6g%-j_BXjOk_Oh%ndG6vG=!+!Q@I-3-)**t5sUZ>?2 zJYwr?GE1~jn^?TFxf%7D zyNjcLm{D$|Jg2b!VD&*u73YaLSO*AxjT{{}UJIK? zTI5M0_WvPzg?1{7SqU}c&nwM=zCT#B@W+~rREa({rHlW>04|URle4!(#DBkhMImCSISP;X-kzw7N6d{EUcIAI=1*4H5py} zd2HqN<4acRF2b@^VIGsKs??n~$iN$-w_N`oAA+nFN zSViqK+!yF_F@k^4F|O3_qdKo~{hquN04oW1)$6MM2r1dFY9hP1yXsqv^hKxFw5;vV z!^T-8IYc%i75ggeDNVFDQ{ARqQ*u&1f|t79WRyBX?x8b%01r<3jz>6w@vKUnU4Eb} z=e2xdKhiA5?@RRr_Q4(X0#AASyo5fc1^vOzLWHtEcBMYH#M_=E+LF%naT*v>zcflm zSOtFt;4cGt+PARMX+ek3?QRq3-E{s*aB4O&>T+CWd8;C@j=id4)6m9y7Kg{!wReg= zekU4jAM|5oGKHAeGL;E?|jJ$+^s`iSwm{%3;RtS)(7V9uWX{eYwC zzCXwE`t=;o_SGG=-zg8*mewV^uB=vhD|Fp>%BD49%>XwV9SoCA`sp!#w=nyk0|7Di zW$5XV#Fb`qjQL$&mm=$FMt#>_TE$x2(JI-4^o)YMXFW9Z$%jhyrTi=kkQX7FQHQSq zeJ*1R;1jWz;qDKKv`bO_VfD%9VAFWb=5;bj8Agk2%C%L&t6%N#wBUMNR>4F%663c~ z?j2>{7q;m+tgMbu4JKMX!wCEQ)Xa~&_l5o)rqlJgUmf|FrlqQ4wF>_7YL>gHcfvSK Ioh=^x2d)9Ah5!Hn 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(); + } +} From 956f81c19fcc5b42ce2e3abe1c411eeae2af6e67 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 4 Nov 2026 00:13:18 +0200 Subject: [PATCH 2/7] Spring.Data.NHibernate5.Integration.Tests: provision SQL Server with Testcontainers Connection strings come from the SPRING_TEST_SQLSERVER_* environment variables. The mangled creditdebit.sql, testObject.sql and RecreateDatabases.sql are replaced by one clean script that also creates the springqa2 login. DbProviderTemplateTests.UserCredentialsDbProvider runs again (its mapping resource name had trailing whitespace). MultipleDbTests.MultipleDBAccess is Explicit (MSDTC), the other test of the fixture is ignored. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LGkLQuAuUjvRjrU1AcTsmk Signed-off-by: Marko Lahma --- .../Data/NHibernate/Dao.xml | 10 +++- .../NHibernate/DbProviderTemplateTests.cs | 1 - ...ibernateTxScopeTransactionManagerTests.xml | 10 +++- .../Data/NHibernate/MultipleDbTests.cs | 4 +- .../Data/NHibernate/MultipleDbTests.xml | 12 ++++- .../Data/NHibernate/NHDAOTests.xml | 16 +++--- ...NHibernate5.Integration.Tests_database.sql | 41 +++++++++++++++ .../Data/NHibernate/creditdebit.sql | 51 ------------------ .../NHibernate/dbProviderTemplateTests.xml | 19 +++---- .../Data/NHibernate/templateTests.xml | 23 ++++---- .../Data/NHibernate/testObject.sql | 24 --------- .../Data/NHibernate/txScopeBugTests.xml | 10 +++- .../RecreateDatabases.sql | 52 ------------------- ....Data.NHibernate5.Integration.Tests.csproj | 4 ++ .../TestAssemblySetup.cs | 41 +++++++++++++++ .../TestEnbeddedConfig.cfg.xml | 12 ----- 16 files changed, 153 insertions(+), 177 deletions(-) create mode 100644 test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/Spring.Data.NHibernate5.Integration.Tests_database.sql delete mode 100644 test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/creditdebit.sql delete mode 100644 test/Spring/Spring.Data.NHibernate5.Integration.Tests/Data/NHibernate/testObject.sql delete mode 100644 test/Spring/Spring.Data.NHibernate5.Integration.Tests/RecreateDatabases.sql create mode 100644 test/Spring/Spring.Data.NHibernate5.Integration.Tests/TestAssemblySetup.cs delete mode 100644 test/Spring/Spring.Data.NHibernate5.Integration.Tests/TestEnbeddedConfig.cfg.xml 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..7c2364b96 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] + [Ignore("4.0: HibernateTemplate.Save throws 'Write operations are not allowed in read-only mode (FlushMode.NEVER)' in a SessionScope session under TxScopeTransactionManager")] 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"> + + + + + + + + + @@ -42,7 +44,7 @@ + value="${SPRING_TEST_SQLSERVER_CS_SPRING}" /> diff --git a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/HibernateTxScopeTransactionManagerNestedTransactionSuspensionTests.cs b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/HibernateTxScopeTransactionManagerNestedTransactionSuspensionTests.cs index 83fef067e..8599c0471 100644 --- a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/HibernateTxScopeTransactionManagerNestedTransactionSuspensionTests.cs +++ b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/HibernateTxScopeTransactionManagerNestedTransactionSuspensionTests.cs @@ -30,6 +30,7 @@ public void CanSuspendTransactionOnNotSupportedWithNestedRequiresNew() } [Test] + [Explicit("Requires MSDTC: the transaction opened under the suspended NotSupported scopes is promoted to a distributed transaction")] public void CanSuspendTransactionOnNotSupportedWithNestedRequired() { Service1.ServiceMethodWithNotSupported3(); diff --git a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests.csproj b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests.csproj index 2f46d591a..946169d3e 100644 --- a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests.csproj +++ b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests_database.sql b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests_database.sql new file mode 100644 index 000000000..dc8bbc830 --- /dev/null +++ b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests_database.sql @@ -0,0 +1,13 @@ +-- The table that Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests map (Mappings.hbm.xml) in the Spring database. +-- The Spring database and the springqa login come from build-support/create-integration-test-databases-and-users.sql. + +USE [Spring] +GO + +CREATE TABLE [dbo].[Container] +( + [Id] [uniqueidentifier] NOT NULL, + [Name] [nvarchar](255) NULL, + CONSTRAINT [PK_Container] PRIMARY KEY CLUSTERED ([Id] ASC) +) +GO diff --git a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.NHibernate.xml b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.NHibernate.xml index 4bdc73764..f48254944 100644 --- a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.NHibernate.xml +++ b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.NHibernate.xml @@ -30,9 +30,7 @@ - - assembly://Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Mappings.hbm.xml - + assembly://Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Mappings.hbm.xml diff --git a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/TestAssemblySetup.cs b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/TestAssemblySetup.cs new file mode 100644 index 000000000..5cabbd7db --- /dev/null +++ b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/TestAssemblySetup.cs @@ -0,0 +1,41 @@ +/* + * 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 +{ + [OneTimeSetUp] + public async Task SetUp() + { + await SqlServerTestEnvironment.InitializeAsync( + [ + "test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests_database.sql", + ]); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await SqlServerTestEnvironment.DisposeAsync(); + } +} From 8961b121d17295ef4eef6bfac9155609b23079ca Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 4 Nov 2026 00:17:00 +0200 Subject: [PATCH 4/7] Spring.Scheduling.Quartz3.Integration.Tests: provision SQL Server with Testcontainers Creates a quartz database with the Quartz 3.22.9 SQL Server tables (vendored tables_sqlServer.sql), resolves the connection string from SPRING_TEST_SQLSERVER_CS_QUARTZ, and replaces the 20 second sleep in the formerly Explicit test with an assertion that the scheduler ran the job. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LGkLQuAuUjvRjrU1AcTsmk Signed-off-by: Marko Lahma --- .../App.config | 4 +- ...ing.Quartz3.Integration.Tests_database.sql | 13 + .../Data/tables_sqlServer.sql | 381 ++++++++++++++++++ .../LocalDataSourceJobStoreTest-core.xml | 3 +- .../Quartz/LocalDataSourceJobStoreTest.cs | 5 +- .../Quartz/LocalDataSourceJobStoreTest.xml | 1 + .../Scheduling/Quartz/TestJob.cs | 6 + ...cheduling.Quartz3.Integration.Tests.csproj | 4 + .../TestAssemblySetup.cs | 45 +++ 9 files changed, 457 insertions(+), 5 deletions(-) create mode 100644 test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/Spring.Scheduling.Quartz3.Integration.Tests_database.sql create mode 100644 test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/tables_sqlServer.sql create mode 100644 test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/TestAssemblySetup.cs diff --git a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/App.config b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/App.config index bd8b6fbf8..9b2297560 100644 --- a/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/App.config +++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/App.config @@ -34,7 +34,9 @@ - + + \ 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..b3a2358a4 --- /dev/null +++ b/test/Spring/Spring.Scheduling.Quartz3.Integration.Tests/Data/Spring.Scheduling.Quartz3.Integration.Tests_database.sql @@ -0,0 +1,13 @@ +-- 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. + +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..da5fc81da 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 @@ -9,11 +9,12 @@ + - + 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..e22031d27 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 @@ -10,6 +10,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(); + } +} From 7c7422328639ea410e13b3bdbd2f74191a32d80b Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 4 Nov 2026 00:19:04 +0200 Subject: [PATCH 5/7] Add the IntegrationTest target and CI job, fix the integration filters, update the docs IntegrationTest runs the four Docker-backed SQL Server projects with TRX reporting (net10.0 off Windows, both TFMs on Windows). Test --test-integration-data now includes those projects (the .Integration filter used to run first and exclude them), and --test-integration-nms matches Spring.Messaging.Nms.Integration.Tests. CI gets an integration-linux job. docker-compose.yml and docker-compose-ci.yml are removed; README and AGENTS.md describe the Testcontainers recipe and the SPRING_TEST_SQLSERVER override. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LGkLQuAuUjvRjrU1AcTsmk Signed-off-by: Marko Lahma --- .github/workflows/ci.yml | 20 ++++++ AGENTS.md | 2 +- README.md | 46 ++++++++++---- build-support/build/Build.Test.cs | 100 +++++++++++++++++++++--------- docker-compose-ci.yml | 12 ---- docker-compose.yml | 12 ---- 6 files changed, 128 insertions(+), 64 deletions(-) delete mode 100644 docker-compose-ci.yml delete mode 100644 docker-compose.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c9c99a8d..157032505 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: failure() + 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/README.md b/README.md index 8473f64f0..6a7674771 100644 --- a/README.md +++ b/README.md @@ -158,17 +158,41 @@ 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): + + ``` + set SPRING_TEST_SQLSERVER=Server=localhost;Integrated Security=true;TrustServerCertificate=true + ./build.cmd IntegrationTest + ``` +- 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..3cd4bfcf7 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,69 @@ 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); - var failedProjects = new System.Collections.Generic.List(); - foreach (var project in testTargets) + RunTestProjects(testTargets); + }); + + void RunTestProjects(IEnumerable testTargets) + { + var failedProjects = new List(); + 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}"); } - 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 From 767e0434c28c6bc757fdd1c4fc863582e0481795 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 4 Nov 2026 00:24:05 +0200 Subject: [PATCH 6/7] Regenerate .fallout/build.schema.json for the IntegrationTest target Produced by running the build, not edited by hand. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LGkLQuAuUjvRjrU1AcTsmk Signed-off-by: Marko Lahma --- .fallout/build.schema.json | 1 + 1 file changed, 1 insertion(+) 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", From 2b707dbd8b3a81fdd0a44eb1d0b46ad06d723f98 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 4 Nov 2026 00:38:51 +0200 Subject: [PATCH 7/7] Integration tests: review follow-ups Run the modules of a project one at a time when SPRING_TEST_SQLSERVER is set (--max-parallel-test-modules 1), because Microsoft.Testing.Platform otherwise starts the net10.0 and net472 builds together and they recreate the same databases under each other. Drop the quartz database before creating it. MultipleDbTests.MultipleDBAccessUsingMultipleSessionScopes is Explicit (MSDTC), not Ignore. Keep the setup exception when disposing the container also throws. Upload the TRX with always(). Convert the Data scripts to UTF-8, drop the redundant EnvironmentVariableMode lines, and tighten the README override section. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LGkLQuAuUjvRjrU1AcTsmk Signed-off-by: Marko Lahma --- .github/workflows/ci.yml | 2 +- README.md | 13 +++++++++++-- build-support/build/Build.Test.cs | 10 +++++++++- test/Shared/SqlServerTestEnvironment.cs | 11 ++++++++++- ...ration.Tests_CreditsAndDebits_database.sql | Bin 886 -> 442 bytes ...ata.Integration.Tests_Credits_database.sql | Bin 506 -> 252 bytes ...Data.Integration.Tests_Debits_database.sql | Bin 494 -> 246 bytes ...Data.Integration.Tests_Spring_database.sql | Bin 4150 -> 2074 bytes .../Data/NHibernate/MultipleDbTests.cs | 2 +- .../App.config | 5 +---- ...ing.Quartz3.Integration.Tests_database.sql | 9 +++++++++ .../LocalDataSourceJobStoreTest-core.xml | 4 +--- .../Quartz/LocalDataSourceJobStoreTest.xml | 4 +--- 13 files changed, 44 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 157032505..373695a90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: Run './build.sh Compile IntegrationTest' run: ./build.sh Compile IntegrationTest - name: Upload test results - if: failure() + if: always() uses: actions/upload-artifact@v7 with: name: integration-test-results diff --git a/README.md b/README.md index 6a7674771..f29a5cc21 100644 --- a/README.md +++ b/README.md @@ -174,12 +174,21 @@ suffice. 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): + 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: ``` - set SPRING_TEST_SQLSERVER=Server=localhost;Integrated Security=true;TrustServerCertificate=true + $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): diff --git a/build-support/build/Build.Test.cs b/build-support/build/Build.Test.cs index 3cd4bfcf7..ee19e7a2a 100644 --- a/build-support/build/Build.Test.cs +++ b/build-support/build/Build.Test.cs @@ -77,6 +77,14 @@ public partial class Build 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; + foreach (var project in testTargets) { // The repo runs tests on Microsoft.Testing.Platform (global.json), where the project goes in --project @@ -89,7 +97,7 @@ void RunTestProjects(IEnumerable testTargets) { if (EnvironmentInfo.IsWin) { - DotNet($"test --project {project.Path} --configuration {Configuration} --no-restore --no-progress --report-trx --results-directory {results}"); + DotNet($"test --project {project.Path} --configuration {Configuration} --no-restore --no-progress --report-trx --results-directory {results} --max-parallel-test-modules {maxParallelModules}"); } else { diff --git a/test/Shared/SqlServerTestEnvironment.cs b/test/Shared/SqlServerTestEnvironment.cs index 3b2203479..db863c925 100644 --- a/test/Shared/SqlServerTestEnvironment.cs +++ b/test/Shared/SqlServerTestEnvironment.cs @@ -131,7 +131,16 @@ public static async Task InitializeAsync(IEnumerable scripts = null, IRe } catch { - await DisposeContainerAsync(); + // 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; } } 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 1848c6f2728713e444d272dd1c75cb5d390fd20b..09263f92fb47e77815a7025ce6cb23b63fe9e7a4 100644 GIT binary patch literal 442 zcmb7=K?=e!5JmSn#VlJ8!6U?ElweJ2GF_AqilqvITF`obTU)7uf@U+Bna}?($sB?g zmtA#euKZldUD;j&TN>t&fNQ4;oh69_qo2b~W(Z}WBx+I%g?88pi<}UM&y&`(D(f)y zPrrykd-_(%0RC`p0`wM?N3+Q+25ktstjUX(pXytu^!sDIZR@*6812Y%r3rkxDjs2- ydur91+Z~hJ zavX?jGBQpD_gqIzr)rXCu{iL3M#5@z2ztkJ1EUAE+K2O$UrObqAFXQEMk%Z9Y}1EP#!#j(PzO5y-<3_Ac?n4_=jM8c5cu=b z_2a8a_9k&iz_gRC_DVT0`nx=OLnvA)P?OwAw9AcKWQ0I$t0q);9-7S$m?kjQGE31w z=pSPPGlc52$cqfLA&74ITFmalJ4(a#Hk_vZFXBc!vP^1%P?hZ*Ao43K56sq3Vhvn* RM`%$%y83`Qo+VjK^#PU6N2&k- literal 506 zcmaKpQES356ot=oe?=cV5Me)|#wcZKyS6@zBI0m@pc`!N?{E8Vs-k5|NOF^V?#($T zzdv(T@`}~S=~Hi7Ypa94v{0n6a#qXNAX3eUGcDQYnlLJEb5334Gx?kvuOaA+)q|>u zMoh1=Q|Gwx^tXzbHPwLbEvvO&^#PmyzYG)Jj*sJ7fxWsQ)1c=aMHXK&sr8~nkLocW z$jt&{+gokDo1IhbVf20O+GwL4H>=|-_d2=YSB1tBm057bI2L-ssmmWY@h?DTnu0E2 f9E0;=cQ|B!XP%jRLvl&Sqf?I*oI_<4X8Wsu82w47 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 dc62fe4d70391e62a61b423bac1d0c37c88a7789..f5c45afe985971ee44f1f804746eae9b3a3a8d43 100644 GIT binary patch literal 246 zcmY+9-3r1m42AFa6uI1B2tGn>qmFqXxY3R*lbRsZRr@?X(Q7NW%r;PA16+AW QXi-4^jc1M*S&<%k0CwU;_y7O^ literal 494 zcmaKp&5FW65QOXOQ{)&y1Ro)rj1tyOG|__u5yce*T@S9W@9x(V|4|ucrl)_Zt84y_ zW0i6WR4LK77Fz39drj3)PdTSWN5ms#*r{gRWA#}j-kjHC=8$+!jRSE;MS_*_PSs^Q z6%TbW9tXZv1e}pRK(9G1VYGuy`w730&cSKjvbT;?{1vnOFpb%_EJm6rR7Wj#lUU~2 z+Fj4pdVw2h3zx$Mv(ic%Jo8hJcQ~&%1$cmY-;dlL)|HC>)^#A|> 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 be0085a2617a138ce97cb0b7649afd209f978af4..14ff405127125db18da1f1bcf1e1b63b244d94dd 100644 GIT binary patch literal 2074 zcmbVNTW^~%6n^JdoJTUDDVbJjFO^Ec=}@W3Y}2ZWP(_2=dP<-p&ZPeP+UDjomuBMy zas2t5?|kP%N)gQIYc0y>%(+S&iWvBqMh`fRV+slGjK9)^q3|IJ5oXaeLc}^V7iNAK zM);~V2L$;H0rM|o6`**mvaj>oe3460mFvuL9Z*kiiHb~t_(H7mW#&$PcrYG86N=rt z6K^sCoG{RwyyoUq)N6_J6;~Jk=JnzwuQT^d-Lf!o{wV~lUYFGy=X(iQuBgmw8Rk-l zE7VI09oYh)vN&=aB$zVdN0`BUcKt920W%Wa_~ah0(LDrlN*N+31b0grp9Ujm)D>b| z?CDh7_o>bJS|*Rb`9cnQ4>nw>OhhTO<1+itI%9+TP=HqZvKN0c{`TFL-_ChE?xCEw zQA5`IvJ3g6uo5$$QP^=k+Byd7ud0pCe*Uzqa`oQM&gQP_)q6k0yaya79n`$ASvTJ& z(r&>2#C!06i1>i`gn3oUT$H@-N7qcurWAiRTok;N;z_8}4?50wy2OlVJIYN@x7D4I zBtT(G`fZ`8sX-fhqKkZ?eL~xIdgPmpaG_ciRHG`VJ~cZc!W0n(YUmOe+^k)F-vb37 z!L1*sh=OZ;dZ2xnIXlFO@}-)m!I@tc!z{N;ZHHgO=n`E;*zlq#Mgg-Z;mXkQKr=It zDM@ZDz}gt^eyZJIFmMhgievlhh(v`4{dV-ATY?@8yUv4tpDA;*xrVr%BRgvEun&!J z=n%wGV*U`v1-pFHncWHbLb%-2G-!{dc`+{G7vN+zNO)@6$9J`g6OpPFGUv_zw5DTloM0 literal 4150 zcmcgvYmd@E6uqDKSM(zqVqBw%UrZ>aAaR!>kKGtz%k;;Z(t>ms*AP-8Jhkx+Wl*utp3ui>EquDz*C|K`*dJn@`aN8IX~GumK`ghj zfbGxV&6V899li-XTEM53@~H-Ez4TL-=}HVKViC&}yJzwp8s>OclXH13b*w#?C%9(4 z8p#K>UWW|Vddhp=HBR;1nQdrY!kPr@H_$&#PyB^nOPu*t-Ff1x^enfY3&bJ z@NglIuw)9qwne~g)ewHsj|9;p_Qtz7{l#b)pWPZl#46r%ccAz<1S^8E(WYD(U z9syUzJ_6sr;rR~FE`Gb@dMZPR3Nt-X?AxEsK5B)DvDLRZ1MxP z7FET0lfS#hd6Oz*_ak3(-`E+<^PcioIinvS1VVJYmvImg0 zQ$&E1M84~qiaAj%oXHQ|r;v|v<=z<>IZO9$=HsN$LtVARc2UkGwkv1rbUt^9VT4-F znm<;xUrz*fw$P~Q{^qI^MV}jMJH0}+GhIGABQ(&9SXDiRVUl(fM#au9>}{OP2FfSh z=_2fKS+fHErsi^j(b_qwT;Z8rE`%<2H)|*5j2-N0x}WWe(-2_|&v9mAd zIb-dr!cRw2W7LE{9o79UUc`!1VxaJH*}}eP(czQ9%xY;XW?W*;rD2tc&$Yb8X`CCZ z9#7sVRIOgCId5NaQeno>qVnv>x5Y`2?_6^o-olT-BQ0^E1n@e5{_r6N^(7FSg@D z77-8g|1t9JU#RFDjDSmW5JLG17OZCsQ^ojSag2EP)%E}2$w;y0H<0&c=FxoQgqdgH TDeyif&s@yfRI^UONyEp#E6`o` 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 7c2364b96..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 @@ -66,7 +66,7 @@ public void MultipleDBAccess() } [Test] - [Ignore("4.0: HibernateTemplate.Save throws 'Write operations are not allowed in read-only mode (FlushMode.NEVER)' in a SessionScope session under TxScopeTransactionManager")] + [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.NestedTxSuspension.Integration.Tests/App.config b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/App.config index 37786bf24..845efd3b7 100644 --- a/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/App.config +++ b/test/Spring/Spring.Data.NHibernate5.NestedTxSuspension.Integration.Tests/App.config @@ -27,10 +27,7 @@ - - - - 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 index b3a2358a4..80ce04a0f 100644 --- 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 @@ -2,6 +2,15 @@ -- 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 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 da5fc81da..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,9 +8,7 @@ - - - + 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 e22031d27..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,9 +9,7 @@ - - - +