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