diff --git a/.editorconfig b/.editorconfig index abfaea7..0098866 100644 --- a/.editorconfig +++ b/.editorconfig @@ -55,7 +55,7 @@ csharp_space_around_binary_operators = before_and_after # ────────────────────────────────────────────── # Expression-bodied members # ────────────────────────────────────────────── -csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_methods = true:suggestion csharp_style_expression_bodied_constructors = false:suggestion csharp_style_expression_bodied_operators = false:suggestion csharp_style_expression_bodied_properties = true:suggestion diff --git a/CHANGELOG.md b/CHANGELOG.md index e8a9b16..9e29e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v5.0.0 +### 💥 Breaking Changes + +- Deleted support for Extensions, use dependency injection instead. +- +### 🐛 Bug Fixes + +- Fixed a bug where SqliteInfrastructure could not delete the file because it was still used by another process. Triggering DoggyDog cleaning. + ## v4.1.1 ### 🛠 Technical @@ -7,6 +16,7 @@ - Internal packages (such as NotoriousTest.Runtimes|Watchdog|SqliteRegistry|TestSettings) are now includes in NotoriousTest. And can no longer be downloaded via Nuget Packages. + ## v4.1.0 ### ✨ Features @@ -18,9 +28,6 @@ "DisableWatchdog": false } } - "Watchdog": { - "ManualLaunch": false - } ``` ### 🛠 Technical @@ -30,7 +37,7 @@ ```json { "Watchdog": { - "ManualLaunch": false + "ManualLaunch": false } } ``` @@ -57,7 +64,7 @@ Launch DoggyDog with the --from-env flag to read those parameters. #### DoggyDog 🐶🐶🐶 💥NEW💥 NotoriousTest now has a new mascot, the DoggyDog ! 🐶🐶🐶 -DoggyDog is a watchdog that clean infrastructures that may have been left dirty by previous tests, +DoggyDog is a watchdog that clean infrastructures that may have been left dirty by previous tests, and make sure that your tests are running in a clean environment. - Introducing DoggyDog - an executable that clean your infrastructures left behind a test campaign that have been killed unexpectedly. @@ -66,17 +73,17 @@ and make sure that your tests are running in a clean environment. #### Infrastructure extensions 💥NEW💥 -**Infrastructure extensions** introduce a composition model for adding behaviors to your infrastructures. Instead of creating specialized subclasses to combine multiple concerns (configuration, database seeding, respawn, etc. ) +**Infrastructure extensions** introduce a composition model for adding behaviors to your infrastructures. Instead of creating specialized subclasses to combine multiple concerns (configuration, database seeding, respawn, etc. ) You register extensions on any infrastructure via `EnsureExtension()`. Each extension is self-contained, reusable across infrastructures, and hooks into the infrastructure lifecycle through dedicated callbacks such as `OnBeforeInitialize`. -- Introducing a new concept called infrastructure extension, meant to be used to react to infrastructure setup. +- Introducing a new concept called infrastructure extension, meant to be used to react to infrastructure setup. - New interface `IInfrastructureExtension`, provide hooks such as `OnBeforeInitialize` to extends Infrastructure. - Configuration is now handled by extensions classes. - Use `EnsureExtension()` or `EnsureExtension(new MyExtension())` to register an extension. -- Built-in extensions : - - Core +- Built-in extensions : + - Core - `OutputConfigurationExtension` : Provide a way to output configuration. Included in `Infrastructure` base class. - `SettingsExtension`: Load from `testsettings.json` your infrastructure configuration. Config key default to infrastructure name, and can be override. - Database @@ -92,8 +99,8 @@ Each extension is self-contained, reusable across infrastructures, and hooks int - Output configuration is now handled by an extension built-in Infrastructure base class. - Adding a configuration output will now be made by calling `AddEntry(key, config)`. - Environment will gather all configuration under all keys and pass to all `IConfigurationConsumer` infrastructures, such as `WebApplicationInfrastructure`. -- `WebApplicationInfrastructure` now maps configuration entries to appsettings format automatically. Generating the section path from the key and config structure. -- e.g. +- `WebApplicationInfrastructure` now maps configuration entries to appsettings format automatically. Generating the section path from the key and config structure. +- e.g. ```json // Entry: "Example:Test" → { "Host": "localhost", "Port": 5432 } // appsettings.json @@ -127,7 +134,7 @@ Each extension is self-contained, reusable across infrastructures, and hooks int - Every framework has it's own `ITestLogger` implementation that is registered in DI. #### Web -- Web support has been moved to `NotoriousTest.Web`. +- Web support has been moved to `NotoriousTest.Web`. - `Environment` now has extensions to retrieve the WebApplication or add a WebApplication. You can find them in the `NotoriousTest.Web` packages, under the `NotoriousTest.Web.Environment.WebEnvironmentExtensions`. - `WebEnvironment` has been deleted. Use extensions to retrieve/add WebApplication. @@ -148,7 +155,7 @@ Each extension is self-contained, reusable across infrastructures, and hooks int - Find them within `NotoriousTest.XUnit`, `NotoriousTest.NUnit`, `NotoriousTest.MSTest` and `NotoriousTest.TUnit` packages. - New samples for every frameworks are available in the samples folder. -## v3.1.0 +## v3.1.0 ### ✨ Features diff --git a/Documentation/2-core-concepts.md b/Documentation/2-core-concepts.md index 073bb56..a18dd39 100644 --- a/Documentation/2-core-concepts.md +++ b/Documentation/2-core-concepts.md @@ -63,190 +63,6 @@ public class MyInfrastructure : Infrastructure --- -## 🧩 Infrastructure Extensions - -**Extensions** introduce a composition model for adding reusable behaviors to your infrastructures without subclassing. Instead of creating specialized subclasses for every concern (configuration output, database seeding, settings loading...), you register extensions via `EnsureExtension()`. - -Each extension hooks into the infrastructure lifecycle through dedicated callbacks: - -```csharp -public interface IInfrastructureExtension -{ - Task OnBeforeInitialize(IInfrastructure infrastructure); - Task OnAfterInitialize(IInfrastructure infrastructure); - Task OnBeforeReset(IInfrastructure infrastructure); - Task OnAfterReset(IInfrastructure infrastructure); - Task OnBeforeDestroy(IInfrastructure infrastructure); - Task OnAfterDestroy(IInfrastructure infrastructure); -} -``` - -Register an extension from within an infrastructure's constructor or `Initialize()`: - -```csharp -public class MyInfrastructure : Infrastructure -{ - public MyInfrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry registry, - ITestSettingsProvider settings) - : base(contextId, logger, registry) - { - // Register a built-in extension - EnsureExtension(new SettingsExtension(settings)); - } -} -``` - -`EnsureExtension()` is idempotent: if an extension of type `T` is already registered, the existing instance is returned. - ---- - -### SettingsExtension - -`SettingsExtension` loads configuration from a `testsettings.json` file into a typed settings object, **before** `Initialize()` runs. - -**1. Create a `testsettings.json`** in your test project and set `Copy to Output Directory: PreserveNewest`: - -```json -{ - "MyInfrastructure": { - "Host": "localhost", - "Port": 1433 - } -} -``` - -**2. Define a settings class:** - -```csharp -public class MySettings -{ - public string Host { get; set; } - public int Port { get; set; } -} -``` - -**3. Register the extension in your infrastructure:** - -```csharp -public class MyInfrastructure : Infrastructure -{ - private SettingsExtension _settings; - - public MyInfrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry registry, - ITestSettingsProvider settingsProvider) - : base(contextId, logger, registry) - { - _settings = EnsureExtension(new SettingsExtension(settingsProvider)); - } - - public override Task Initialize() - { - // Settings are loaded before Initialize() is called - var host = _settings.Settings.Host; - var port = _settings.Settings.Port; - return Task.CompletedTask; - } -} -``` - -📌 **Key Points:** -- The section key defaults to the infrastructure's class name. You can override it via the `SectionName` property. -- `ITestSettingsProvider` is automatically registered in the DI container by the environment. - ---- - -### OutputConfigurationExtension - -`OutputConfigurationExtension` allows an infrastructure to **expose configuration** (e.g., a connection string, a base URL) that other infrastructures or the web application can consume. - -It is built into the `Infrastructure` base class, so you don't need to register it manually when you use that base class. - -**Example:** an infrastructure that outputs a connection string: - -```csharp -public class MyDatabaseInfrastructure : Infrastructure -{ - public MyDatabaseInfrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry registry) - : base(contextId, logger, registry) { } - - public override async Task Initialize() - { - // ... start database ... - string connectionString = "Server=localhost;Database=..."; - - // Expose a configuration entry — the key is free-form, interpreted by the consumer - AddEntry("ConnectionStrings:MyDb", new ConnectionStringConfig(connectionString)); - } -} -``` - -📌 **Key Points:** -- The key is a free-form string — its meaning is entirely determined by the **consumer**. -- The environment collects all `ConfigurationEntry` objects and passes them as a flat list to every `IConfigurationConsumer` infrastructure. -- **`WebApplicationInfrastructure`** (from `NotoriousTest.Web`) is the built-in consumer: it interprets keys as appsettings paths and maps entries accordingly: - -```json -// Entry key: "Example:Database" → value: { "Host": "localhost", "Port": 5432 } -// Injected into the web app as: -{ - "Example": { - "Database": { - "Host": "localhost", - "Port": 5432 - } - } -} -``` - -Any infrastructure implementing `IConfigurationConsumer` receives the same list and can interpret the keys however it needs. - ---- - -### Custom Extensions - -An extension is a self-contained class that encapsulates a specific, reusable behavior. The canonical use case is to **strongly type it against the infrastructure it targets** using `IInfrastructureExtension`, giving it direct access to all infrastructure members. - -**Example:** a seeding extension that runs reference data inserts after a database is initialized: - -```csharp -public class ReferenceDataSeedExtension : IInfrastructureExtension -{ - public async Task OnAfterInitialize(MyDatabaseInfrastructure infrastructure) - { - using var connection = infrastructure.GetDatabaseConnection(); - await connection.OpenAsync(); - - using var command = connection.CreateCommand(); - command.CommandText = @" - INSERT INTO Countries (Code, Name) VALUES ('FR', 'France'); - INSERT INTO Countries (Code, Name) VALUES ('DE', 'Germany'); - INSERT INTO Countries (Code, Name) VALUES ('US', 'United States'); - "; - await command.ExecuteNonQueryAsync(); - } -} -``` - -Register it in your infrastructure: - -```csharp -public class MyDatabaseInfrastructure : Infrastructure -{ - public MyDatabaseInfrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry registry) - : base(contextId, logger, registry) - { - EnsureExtension(); - } -} -``` - -📌 **Key Points:** -- The extension has its own state and dependencies — it is a proper class, not a delegate wrapper. -- `IInfrastructureExtension` gives the extension typed access to the infrastructure without casting. -- Any hook not overridden has a default no-op implementation — only implement what you need. - ---- - ## ⚙️ Configuration Propagation `OutputConfigurationExtension` and `Infrastructure` are built on top of two simple interfaces that define how infrastructures communicate configuration to each other: diff --git a/DoggyDog/Arguments/ArgumentParser.cs b/DoggyDog/Arguments/ArgumentParser.cs index 8ef028f..72ec3ea 100644 --- a/DoggyDog/Arguments/ArgumentParser.cs +++ b/DoggyDog/Arguments/ArgumentParser.cs @@ -14,25 +14,23 @@ public static class ArgumentsParser return ParseInternal(prop => dict.TryGetValue(prop, out var v) ? v : null); } - public static T ParseFromEnv(string envPrefix) where T : new() - { - return ParseInternal(prop => + public static T ParseFromEnv(string envPrefix) where T : new() => + ParseInternal(prop => { - var envKey = $"{envPrefix}_{prop.Replace("-", "_").ToUpperInvariant()}"; + string envKey = $"{envPrefix}_{prop.Replace("-", "_").ToUpperInvariant()}"; return Environment.GetEnvironmentVariable(envKey, EnvironmentVariableTarget.User); }); - } private static T ParseInternal(Func resolver) where T : new() { - var errors = 0; + int errors = 0; var instance = new T(); - foreach (var prop in typeof(T).GetProperties()) + foreach (PropertyInfo prop in typeof(T).GetProperties()) { - var attr = prop.GetCustomAttribute(); + CliArgumentAttribute? attr = prop.GetCustomAttribute(); if (attr is null) continue; - var raw = resolver(attr.Name); + string? raw = resolver(attr.Name); if (raw is null) { @@ -43,13 +41,13 @@ public static class ArgumentsParser try { - var targetType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; + Type targetType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; object converted = targetType switch { - _ when targetType == typeof(Guid) => Guid.Parse(raw.ToString()!), - _ when targetType == typeof(DateTimeOffset) => DateTimeOffset.Parse(raw.ToString()!), - _ when targetType.IsEnum => Enum.Parse(targetType, raw.ToString()!), + _ when targetType == typeof(Guid) => Guid.Parse(raw), + _ when targetType == typeof(DateTimeOffset) => DateTimeOffset.Parse(raw), + _ when targetType.IsEnum => Enum.Parse(targetType, raw), _ when targetType == typeof(string[]) => raw.Split("|"), _ => Convert.ChangeType(raw, targetType) }; @@ -72,4 +70,4 @@ public static class ArgumentsParser return instance; } -} \ No newline at end of file +} diff --git a/DoggyDog/DoggyDog.csproj b/DoggyDog/DoggyDog.csproj index 6f48260..568a89c 100644 --- a/DoggyDog/DoggyDog.csproj +++ b/DoggyDog/DoggyDog.csproj @@ -22,9 +22,7 @@ - + diff --git a/DoggyDog/DoggyDogRecoveryWatchdog.cs b/DoggyDog/DoggyDogRecoveryWatchdog.cs index 83f2dd4..4adf86a 100644 --- a/DoggyDog/DoggyDogRecoveryWatchdog.cs +++ b/DoggyDog/DoggyDogRecoveryWatchdog.cs @@ -14,17 +14,17 @@ namespace DoggyDog internal class DoggyDogRecoveryWatchdog { private const string BANNER = """ - |\ - \`-. _.._| \ - |_,' __`. \ ██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ - (.\ _/.| _ | ██╔══██╗██╔═══██╗██╔════╝ ██╔════╝╚ ██╗ ██╔╝ ██╔══██╗██╔═══██╗██╔════╝ + |\ + \`-. _.._| \ + |_,' __`. \ ██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ + (.\ _/.| _ | ██╔══██╗██╔═══██╗██╔════╝ ██╔════╝╚ ██╗ ██╔╝ ██╔══██╗██╔═══██╗██╔════╝ ,' __ \ | ██║ ██║██║ ██║██║ ███╗██║ ███╗ ╚████╔╝ ██║ ██║██║ ██║██║ ███╗ ,' __/||\ | ██║ ██║██║ ██║██║ ██║██║ ██║ ╚██╔╝ ██║ ██║██║ ██║██║ ██║ (..) ,/|||||/ | ██████╔╝╚██████╔╝╚██████╔╝╚██████╔╝ ██║ ██████╔╝╚██████╔╝╚██████╔╝ - `-'_---- _.\ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ + `-'_---- _.\ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ /`-._.-'_.- -- .NET INTEGRATION TEST CRASH RECOVERY WATCHDOG -- `-.__.-' - + """; private readonly ITestAssemblyLoader _assemblyLoader; private readonly IRegistry _registry; @@ -39,7 +39,7 @@ public DoggyDogRecoveryWatchdog(ITestAssemblyLoader assemblyLoader, IRegistry re public static void Banner(int pid, Guid environmentId) { LogColorScope.DarkMagenta(() => Console.WriteLine(BANNER)); - var version = Assembly.GetExecutingAssembly().GetName().Version!; + Version version = Assembly.GetExecutingAssembly().GetName().Version!; LogColorScope.DarkGray(() => { Console.WriteLine($" Monitoring PID {pid} · EID {environmentId} · v{version.Major}.{version.Minor}.{version.Build}"); @@ -54,14 +54,14 @@ public async Task Run(int pid, Guid environmentId) using (var source = new CancellationTokenSource()) { _logger.Debug("Watching registry for infrastructures updates."); - Task watcherTask = Task.Run(() => _registry.Watch(environmentId, source.Token)); + var watcherTask = Task.Run(() => _registry.Watch(environmentId, source.Token), source.Token); _assemblyLoader.Load(); _logger.Info($"Attached to test process with PID {pid} and EID {environmentId}"); await WaitForTestProcess(pid); - source.Cancel(); + await source.CancelAsync(); await watcherTask; } diff --git a/DoggyDog/Logs/Logger.cs b/DoggyDog/Logs/Logger.cs index 0793e9d..9c2e1f5 100644 --- a/DoggyDog/Logs/Logger.cs +++ b/DoggyDog/Logs/Logger.cs @@ -5,15 +5,9 @@ namespace DoggyDog.Logs public class Logger { - private static Logger _logger; + private static Logger? _logger; - public static Logger Instance - { - get - { - return _logger ??= new Logger(); - } - } + public static Logger Instance => _logger ??= new Logger(); private readonly Stack _scopeStack = new(); public static LogLevel MinLogLevel { get; set; } = LogLevel.Info; @@ -24,17 +18,15 @@ public LogScope CreateScope(string scopeName, string? message = null) return new LogScope(this, scopeName); } - public void ExitScope(string name) - { - _scopeStack.TryPop(out _); - } + public void ExitScope(string name) => _scopeStack.TryPop(out _); + public void Log(LogLevel logType, string message, Exception? exception = null) { if (logType > MinLogLevel) return; - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.Append(" > "); - var scopeString = string.Join(string.Empty, _scopeStack.Reverse().Select(scope => $"[{scope}]").ToArray()); + string scopeString = string.Join(string.Empty, _scopeStack.Reverse().Select(scope => $"[{scope}]").ToArray()); stringBuilder.Append(scopeString); @@ -51,7 +43,7 @@ public void Log(LogLevel logType, string message, Exception? exception = null) colorScope(() => { - stringBuilder.Append(" "); + stringBuilder.Append(' '); stringBuilder.Append(message); if (exception != null) stringBuilder.Append(exception.ToString()); Console.WriteLine(stringBuilder.ToString()); @@ -67,10 +59,7 @@ public void Log(LogLevel logType, string message, Exception? exception = null) } public class LogScope(Logger logger, string name) : IDisposable { - public void Dispose() - { - logger.ExitScope(name); - } + public void Dispose() => logger.ExitScope(name); } public enum LogLevel { diff --git a/DoggyDog/Program.cs b/DoggyDog/Program.cs index 8e59407..1940a72 100644 --- a/DoggyDog/Program.cs +++ b/DoggyDog/Program.cs @@ -6,17 +6,18 @@ using NotoriousTest.SqlLiteRegistry; + Logger logger = Logger.Instance; try { using (logger.CreateScope("DoggyDog")) { - WatchDogParameters parameters; - if (args.Contains("--from-env")) - parameters = ArgumentsParser.ParseFromEnv("DOGGYDOG_DEBUG"); - else - parameters = ArgumentsParser.Parse(args); + logger.Debug("Parsing parameters from environment variables"); + + WatchDogParameters parameters = args.Contains("--from-env") + ? ArgumentsParser.ParseFromEnv("DOGGYDOG_DEBUG") + : ArgumentsParser.Parse(args); if (parameters.LogLevel != null) Logger.MinLogLevel = parameters.LogLevel.Value; @@ -24,7 +25,7 @@ DoggyDogRecoveryWatchdog.Banner(parameters.Pid, parameters.EnvironmentId); SqliteRegistryProvider registry = GetRegistry(parameters); - TestAssemblyLoader assemblyLoader = new TestAssemblyLoader(parameters.AssemblyPath, parameters.RuntimesPath); + var assemblyLoader = new TestAssemblyLoader(parameters.AssemblyPath, parameters.RuntimesPath); var watchdog = new DoggyDogRecoveryWatchdog(assemblyLoader, registry, logger); await watchdog.Run(parameters.Pid, parameters.EnvironmentId); @@ -54,9 +55,9 @@ static SqliteRegistryProvider GetRegistry(WatchDogParameters parameters) Logger.Instance.Info($"Using registry file at {cs.DataSource}"); } - SqliteRegistryProvider registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration() + var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration() { ConnectionString = parameters.ConnectionString }); return registry; -} \ No newline at end of file +} diff --git a/NotoriousTest.Core/Environments/EnvironmentBase.cs b/NotoriousTest.Core/Environments/EnvironmentBase.cs index ebbc3ea..e1f4d14 100644 --- a/NotoriousTest.Core/Environments/EnvironmentBase.cs +++ b/NotoriousTest.Core/Environments/EnvironmentBase.cs @@ -28,17 +28,14 @@ public abstract class EnvironmentBase protected IServiceProvider ServiceProvider { get; private set; } private IServiceCollection _serviceCollection; - protected IWatchDog WatchDog => _watchDog ??= ServiceProvider.GetRequiredService(); - private IWatchDog? _watchDog; - protected IRegistry Registry => _registry ??= ServiceProvider.GetRequiredService(); - private IRegistry? _registry; - protected IRuntime Runtime => _runtime ??= ServiceProvider.GetRequiredService(); - private IRuntime? _runtime; - protected ITestLogger Logger => _logger ??= ServiceProvider.GetRequiredService(); - private ITestLogger _logger; + protected IWatchDog WatchDog => field ??= ServiceProvider.GetRequiredService(); + protected IRegistry Registry => field ??= ServiceProvider.GetRequiredService(); + protected IRuntime Runtime => field ??= ServiceProvider.GetRequiredService(); + protected ITestLogger Logger => field ??= ServiceProvider.GetRequiredService(); - protected EnvironmentConfiguration Settings => _settings ??= ServiceProvider.GetRequiredService()?.Get(EnvironmentConfiguration.SECTION_NAME) ?? new EnvironmentConfiguration(); - private EnvironmentConfiguration _settings; + protected EnvironmentConfiguration Settings => field ??= + ServiceProvider.GetRequiredService() + ?.Get(EnvironmentConfiguration.SECTION_NAME) ?? new EnvironmentConfiguration(); /// @@ -55,10 +52,7 @@ public abstract class EnvironmentBase /// Configuration infrastructure dependency injection. /// /// - public virtual void ConfigureInfrastructureServices(IServiceCollection collection) - { - _serviceCollection.AddSingleton(EnvironmentId); - } + protected virtual void ConfigureInfrastructureServices(IServiceCollection collection) => _serviceCollection.AddSingleton(EnvironmentId); /// /// Configure environment with infrastructures. Called before environment initialization. @@ -161,6 +155,7 @@ public virtual async Task Reset() public virtual async Task Destroy() { + await ExecuteActionOnInfrastructureInParralelAndInOrder((i) => i.DestroyAsync()); if (!Settings.DisableWatchdog) @@ -186,10 +181,6 @@ private List> AggregateInfrastructureConfiguration() .ToList(); } - private async Task SetupRegistry() - { - var registry = ServiceProvider.GetRequiredService(); - await registry.Ensure(); - } + private Task SetupRegistry() => Registry.Ensure(); } } diff --git a/NotoriousTest.Core/Extensions/IInfrastructureExtension.cs b/NotoriousTest.Core/Extensions/IInfrastructureExtension.cs deleted file mode 100644 index 8968ea9..0000000 --- a/NotoriousTest.Core/Extensions/IInfrastructureExtension.cs +++ /dev/null @@ -1,85 +0,0 @@ -using NotoriousTest.Core.Infrastructures; - -public interface IInfrastructureExtension -{ - /// - /// Called before the infrastructure is initialized. - /// - Task OnBeforeInitialize(IInfrastructure infrastructure) => Task.CompletedTask; - - /// - /// Called after the infrastructure is initialized. - /// - Task OnAfterInitialize(IInfrastructure infrastructure) => Task.CompletedTask; - - /// - /// Called before the infrastructure is reset. - /// - Task OnBeforeReset(IInfrastructure infrastructure) => Task.CompletedTask; - - /// - /// Called after the infrastructure is reset. - /// - Task OnAfterReset(IInfrastructure infrastructure) => Task.CompletedTask; - - /// - /// Called before the infrastructure is destroyed. - /// - Task OnBeforeDestroy(IInfrastructure infrastructure) => Task.CompletedTask; - - /// - /// Called after the infrastructure is destroyed. - /// - Task OnAfterDestroy(IInfrastructure infrastructure) => Task.CompletedTask; -} - -public interface IInfrastructureExtension : IInfrastructureExtension where T : IInfrastructure -{ - Task IInfrastructureExtension.OnBeforeInitialize(IInfrastructure infrastructure) - => infrastructure is T typed ? OnBeforeInitialize(typed) : throw new InvalidOperationException($"Infrastructure must be of type {typeof(T)}"); - - Task IInfrastructureExtension.OnAfterInitialize(IInfrastructure infrastructure) - => infrastructure is T typed ? OnAfterInitialize(typed) : throw new InvalidOperationException($"Infrastructure must be of type {typeof(T)}"); - - Task IInfrastructureExtension.OnBeforeReset(IInfrastructure infrastructure) - => infrastructure is T typed ? OnBeforeReset(typed) : throw new InvalidOperationException($"Infrastructure must be of type {typeof(T)}"); - - Task IInfrastructureExtension.OnAfterReset(IInfrastructure infrastructure) - => infrastructure is T typed ? OnAfterReset(typed) : throw new InvalidOperationException($"Infrastructure must be of type {typeof(T)}"); - - Task IInfrastructureExtension.OnBeforeDestroy(IInfrastructure infrastructure) - => infrastructure is T typed ? OnBeforeDestroy(typed) : throw new InvalidOperationException($"Infrastructure must be of type {typeof(T)}"); - - Task IInfrastructureExtension.OnAfterDestroy(IInfrastructure infrastructure) - => infrastructure is T typed ? OnAfterDestroy(typed) : throw new InvalidOperationException($"Infrastructure must be of type {typeof(T)}"); - - /// - /// Called before the infrastructure is initialized. - /// - new Task OnBeforeInitialize(T infrastructure) => Task.CompletedTask; - - /// - /// Called after the infrastructure is initialized. - /// - new Task OnAfterInitialize(T infrastructure) => Task.CompletedTask; - - /// - /// Called before the infrastructure is reset. - /// - new Task OnBeforeReset(T infrastructure) => Task.CompletedTask; - - /// - /// Called after the infrastructure is reset. - /// - new Task OnAfterReset(T infrastructure) => Task.CompletedTask; - - /// - /// Called before the infrastructure is destroyed. - /// - new Task OnBeforeDestroy(T infrastructure) => Task.CompletedTask; - - /// - /// Called after the infrastructure is destroyed. - /// - new Task OnAfterDestroy(T infrastructure) => Task.CompletedTask; -} diff --git a/NotoriousTest.Core/Extensions/InfrastructureSettingsNotFound.cs b/NotoriousTest.Core/Extensions/InfrastructureSettingsNotFound.cs deleted file mode 100644 index 33d0823..0000000 --- a/NotoriousTest.Core/Extensions/InfrastructureSettingsNotFound.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Runtime.Serialization; - -namespace NotoriousTest.Core.Extensions -{ - [Serializable] - public class InfrastructureSettingsNotFound : Exception - { - public InfrastructureSettingsNotFound() - { - } - - public InfrastructureSettingsNotFound(string message) : base(message) - { - } - - public InfrastructureSettingsNotFound(string message, Exception innerException) : base(message, innerException) - { - } - - protected InfrastructureSettingsNotFound(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } -} diff --git a/NotoriousTest.Core/Infrastructures/Infrastructure.cs b/NotoriousTest.Core/Infrastructures/Infrastructure.cs index c620c59..0e212bc 100644 --- a/NotoriousTest.Core/Infrastructures/Infrastructure.cs +++ b/NotoriousTest.Core/Infrastructures/Infrastructure.cs @@ -51,7 +51,6 @@ public abstract class Infrastructure : IAsyncDisposable, IInfrastructure /// public EnvironmentId EnvironmentId { get; set; } public Guid Id = Guid.NewGuid(); - private readonly List _extensions = new(); /// /// Gets or sets the metadata associated with the current object. @@ -93,21 +92,10 @@ internal async Task InitializeAsync() { Logger.Log($"[{GetType().Name}] Initialization ..."); var sw = Stopwatch.StartNew(); - foreach (var extension in _extensions) - { - Logger.Log($"[{extension.GetType().Name}] OnBeforeInitialize"); - await extension.OnBeforeInitialize(this); - } await Initialize(); if (!WatchdogDisabled && !DisableRegistry && !Registered) await Register(); - - foreach (var extension in _extensions) - { - Logger.Log($"[{extension.GetType().Name}] OnAfterInitialize"); - await extension.OnAfterInitialize(this); - } Logger.Log($"[{GetType().Name}] Initialization completed in {sw.ElapsedMilliseconds} ms"); } @@ -137,20 +125,10 @@ internal async Task ResetAsync() { Logger.Log($"[{GetType().Name}] Reset ..."); var sw = Stopwatch.StartNew(); - foreach (var extension in _extensions) - { - Logger.Log($"[{extension.GetType().Name}] OnBeforeReset"); - await extension.OnBeforeReset(this); - } await Reset(); if (!WatchdogDisabled && !DisableRegistry) await Registry.NotifyReset(Id); - foreach (var extension in _extensions) - { - Logger.Log($"[{extension.GetType().Name}] OnAfterReset"); - await extension.OnAfterReset(this); - } - Logger.Log($"[{GetType().Name} ] Reset completed in "); + Logger.Log($"[{GetType().Name} ] Reset completed in {sw.ElapsedMilliseconds} ms"); } @@ -159,51 +137,9 @@ internal async Task DestroyAsync() Logger.Log($"[{GetType().Name}] Destroy ..."); var sw = Stopwatch.StartNew(); - foreach (var extension in _extensions) - { - Logger.Log($"[{extension.GetType().Name}] OnBeforeDestroy"); - await extension.OnBeforeDestroy(this); - } - await Destroy(); if (!WatchdogDisabled && !DisableRegistry) await Registry.Remove(Id); - foreach (var extension in _extensions) - { - Logger.Log($"[{extension.GetType().Name}] OnAfterDestroy"); - await extension.OnAfterDestroy(this); - } - - Logger.Log($"[{GetType().Name} ] Destroy completed in "); - } - - /// - /// Ensures that an extension of the specified type is present in the collection, returning the existing instance if - /// found or adding and returning the provided instance if not. - /// - /// The type of the infrastructure extension to ensure. Must implement IInfrastructureExtension. - /// The extension instance to add if an existing instance of type T is not already present. Cannot be null. - /// The existing extension of type T if present; otherwise, the provided extension instance. - public T EnsureExtension(T extension) where T : IInfrastructureExtension - { - var existing = _extensions.OfType().FirstOrDefault(); - if (existing != null) return existing; - - _extensions.Add(extension); - return extension; - } - - /// - /// Retrieves an existing extension of the specified type from the collection, or creates and adds a new instance if - /// none exists. - /// - /// The type of extension to retrieve or create. Must implement IInfrastructureExtension and have a parameterless - /// constructor. - /// An instance of the specified extension type. If an extension of this type already exists in the collection, it - /// is returned; otherwise, a new instance is created, added to the collection, and returned. - public T EnsureExtension() where T : IInfrastructureExtension, new() - { - T extension = new T(); - return EnsureExtension(extension); + Logger.Log($"[{GetType().Name} ] Destroy completed in {sw.ElapsedMilliseconds} ms"); } } diff --git a/NotoriousTest.Core/Infrastructures/InfrastructureSettingsNotFound.cs b/NotoriousTest.Core/Infrastructures/InfrastructureSettingsNotFound.cs index c19c92a..5b859a4 100644 --- a/NotoriousTest.Core/Infrastructures/InfrastructureSettingsNotFound.cs +++ b/NotoriousTest.Core/Infrastructures/InfrastructureSettingsNotFound.cs @@ -3,7 +3,7 @@ namespace NotoriousTest.Core.Infrastructures { [Serializable] - internal class InfrastructureSettingsNotFound : Exception + public class InfrastructureSettingsNotFound : Exception { public InfrastructureSettingsNotFound() { diff --git a/NotoriousTest.Database/DatabaseInfrastructureBase.cs b/NotoriousTest.Database/DatabaseInfrastructureBase.cs index 53d08e9..6bd8c7b 100644 --- a/NotoriousTest.Database/DatabaseInfrastructureBase.cs +++ b/NotoriousTest.Database/DatabaseInfrastructureBase.cs @@ -4,6 +4,8 @@ using NotoriousTest.Core.Registry; using System.Data.Common; +using Respawn; +using Respawn.Graph; namespace NotoriousTest.Database { @@ -14,6 +16,10 @@ public abstract class DatabaseInfrastructureBase $"{DbPrefix}_{EnvironmentId.Value}"; public string[] TableToIgnore { get; init; } = []; public string[] TableToInclude { get; init; } = []; + public string[] SchemasToInclude { get; init; } = []; + public string[] SchemasToExclude { get; init; } = []; + + private Respawner? _respawner; public DatabaseInfrastructureBase(EnvironmentId contextId, ITestLogger logger, IRegistry registry) : base(contextId, logger, registry) @@ -26,15 +32,9 @@ public DatabaseInfrastructureBase(EnvironmentId contextId, ITestLogger logger, I /// Returns a SQL Server connection connected to the current infrastructure's database. /// /// A SqlConnection instance connected to the current infrastructure's database. - public DbConnection GetDatabaseConnection() - { - return GetConnection(GetDatabaseConnectionString()); - } + public DbConnection GetDatabaseConnection() => GetConnection(GetDatabaseConnectionString()); - public DbConnection GetServerConnection() - { - return GetConnection(GetServerConnectionString()); - } + public DbConnection GetServerConnection() => GetConnection(GetServerConnectionString()); /// /// Returns a SQL Server connection string pointing to the current infrastructure's database. @@ -45,14 +45,44 @@ public DbConnection GetServerConnection() public override async Task Initialize() { - using var connection = GetServerConnection(); + await using DbConnection connection = GetServerConnection(); await connection.OpenAsync(); await CreateDatabase(connection); + + await using DbConnection databaseConnection = GetDatabaseConnection(); + await databaseConnection.OpenAsync(); + } + + public override async Task Reset() + { + + try + { + await using DbConnection connection = GetDatabaseConnection(); + await connection.OpenAsync(); + + _respawner ??= await Respawner.CreateAsync(connection, new RespawnerOptions() + { + TablesToIgnore = TableToIgnore.Select(tti => new Table(tti)).ToArray(), + TablesToInclude = TableToInclude.Select(tti => new Table(tti)).ToArray(), + SchemasToExclude = SchemasToExclude, + SchemasToInclude = SchemasToInclude + }); + + await _respawner.ResetAsync(connection); + } + catch (InvalidOperationException ex) + { + // This can occur if the database has no tables. In that case, we can ignore the exception and continue with the test setup. + Logger.Log(ex.Message); + } + + } public override async Task Destroy() { - using var connection = GetServerConnection(); + await using DbConnection connection = GetServerConnection(); await connection.OpenAsync(); await DropDatabase(connection); diff --git a/NotoriousTest.Database/ExternalDatabaseInfrastructure.cs b/NotoriousTest.Database/ExternalDatabaseInfrastructure.cs index 21ef207..df0bebb 100644 --- a/NotoriousTest.Database/ExternalDatabaseInfrastructure.cs +++ b/NotoriousTest.Database/ExternalDatabaseInfrastructure.cs @@ -1,5 +1,5 @@ using NotoriousTest.Core; -using NotoriousTest.Core.Extensions; +using NotoriousTest.Core.Infrastructures; using NotoriousTest.Core.Logger; using NotoriousTest.Core.Registry; using NotoriousTest.Core.Settings; @@ -17,10 +17,7 @@ public ExternalDatabaseInfrastructure(EnvironmentId contextId, ITestSettingsProv Settings = provider.Get(SectionName ?? this.GetType().Name) ?? throw new InfrastructureSettingsNotFound($"Settings in section {SectionName ?? this.GetType().Name} not found."); } - public override string GetServerConnectionString() - { - return Settings.ConnectionString; - } + public override string GetServerConnectionString() => Settings.ConnectionString; public override async Task Initialize() { diff --git a/NotoriousTest.Database/RespawnExtension.cs b/NotoriousTest.Database/RespawnExtension.cs deleted file mode 100644 index 6778797..0000000 --- a/NotoriousTest.Database/RespawnExtension.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Respawn; - -namespace NotoriousTest.Database -{ - public class RespawnExtension : IInfrastructureExtension - { - private readonly Func? _optionsFactory; - RespawnerOptions? _options => _optionsFactory?.Invoke(); - private Respawner? _respawner; - - public RespawnExtension(Func? optionsFactory = null) - { - _optionsFactory = optionsFactory; - } - - public async Task OnBeforeReset(IDatabaseInfrastructure infra) - { - try - { - using var connection = infra.GetDatabaseConnection(); - await connection.OpenAsync(); - if (_respawner == null) _respawner = await Respawner.CreateAsync(connection, _options); - await _respawner.ResetAsync(connection); - } - catch (InvalidOperationException exception) - { - // This can occur if the database has no tables. In that case, we can ignore the exception and continue with the test setup. - } - catch (Exception ex) - { - throw new Exception("Failed to reset database using Respawn.", ex); - } - } - } -} \ No newline at end of file diff --git a/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogEnvironment.cs b/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogEnvironment.cs deleted file mode 100644 index ebab74e..0000000 --- a/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogEnvironment.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NotoriousTest.Sqlite; - -using System.Reflection; - -using Xunit.Sdk; - -namespace NotoriousTest.IntegrationTests.DoggyDog -{ - public class DoggyDogEnvironment : XUnit.Environment - { - public DoggyDogEnvironment(IMessageSink sink) : base(sink) - { - } - - public override Assembly CurrentAssembly => Assembly.GetExecutingAssembly(); - - public override async Task ConfigureEnvironment() - { - AddInfrastructure(); - } - } -} diff --git a/NotoriousTest.IntegrationTests/DoggyDogTestFramework.cs b/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTestFramework.cs similarity index 75% rename from NotoriousTest.IntegrationTests/DoggyDogTestFramework.cs rename to NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTestFramework.cs index 5d2c434..a95806f 100644 --- a/NotoriousTest.IntegrationTests/DoggyDogTestFramework.cs +++ b/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTestFramework.cs @@ -1,4 +1,5 @@ -using AwesomeAssertions; +using System.Data.Common; +using AwesomeAssertions; using Dapper; @@ -21,27 +22,13 @@ public static class DoggyDogTestFramework { public static class Arrange { - public const string ENSURE_REGISTRY = @$" - CREATE TABLE IF NOT EXISTS InfrastructureRegistry( - InfrastructureId TEXT PRIMARY KEY, - InfrastructureType TEXT NOT NULL, - EnvironmentId TEXT NOT NULL, - ProcessID TEXT NOT NULL, - Metadata TEXT, - MetadataType TEXT, - CreationDate TEXT NOT NULL, - UpdateDate TEXT NOT NULL, - LastResetDate TEXT - ) - "; - public const string REGISTER_INFRASTRUCTURE = @$" INSERT INTO InfrastructureRegistry(InfrastructureId, InfrastructureType, EnvironmentId, ProcessID, Metadata, MetadataType, CreationDate, UpdateDate, LastResetDate) VALUES(@InfrastructureId, @InfrastructureType, @EnvironmentId, @ProcessID, @Metadata, @MetadataType, datetime('now', 'utc'), datetime('now', 'utc'), null) RETURNING * "; - public static Process? StartFakeProcess(Guid environmentId, int exitCode = 0, int timeToExit = 5) + public static Process StartFakeProcess(Guid environmentId, int exitCode = 0, int timeToExit = 5) { ProcessStartInfo startInfo = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new ProcessStartInfo @@ -59,8 +46,14 @@ INSERT INTO InfrastructureRegistry(InfrastructureId, InfrastructureType, Environ CreateNoWindow = false, }; - Process? fakeProcess = Process.Start(startInfo); - if (exitCode == 0 && fakeProcess != null) + var fakeProcess = Process.Start(startInfo); + + if (fakeProcess == null) + { + Xunit.Assert.Fail("Fail to launch cmd.exe"); + } + + if (exitCode == 0) { File.WriteAllText(Path.Combine(Path.GetTempPath(), $"nt-{environmentId}.signal"), "OK"); } @@ -68,16 +61,10 @@ INSERT INTO InfrastructureRegistry(InfrastructureId, InfrastructureType, Environ return fakeProcess; } - public static async Task CreateRegistry(SqliteInfrastructure registryInfrastructure) - { - using var connection = registryInfrastructure.GetDatabaseConnection(); - await connection.ExecuteAsync(ENSURE_REGISTRY); - } - public static async Task CreateInfrastructureInRegistry(SqliteInfrastructure registryInfrastructure, Process attachedProcess, Type fakeInfrastructureType, Guid environmentId) { - using var connection = registryInfrastructure.GetDatabaseConnection(); + await using DbConnection connection = registryInfrastructure.GetDatabaseConnection(); var entry = new InfrastuctureRegistryEntry { @@ -88,89 +75,68 @@ public static async Task CreateInfrastructureInRegis Metadata = "Test passed !" }; - var entity = await connection.QuerySingleAsync(REGISTER_INFRASTRUCTURE, InfrastructureRegistryEntryEntity.FromDomain(entry)); + InfrastructureRegistryEntryEntity entity = await connection.QuerySingleAsync(REGISTER_INFRASTRUCTURE, InfrastructureRegistryEntryEntity.FromDomain(entry)); return entity.ToDomain(); } public static async Task TriggerInfrastructureReset(SqliteInfrastructure registryInfrastructure, Guid infrastructureId) { - using var connection = registryInfrastructure.GetDatabaseConnection(); + await using DbConnection connection = registryInfrastructure.GetDatabaseConnection(); await connection.ExecuteAsync("UPDATE InfrastructureRegistry SET LastResetDate = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE InfrastructureId = @InfrastructureId", new { InfrastructureId = infrastructureId.ToString() }); } public static async Task DestroyInfrastructureFromRegistry(SqliteInfrastructure registryInfrastructure, Guid infrastructureId) { - using var connection = registryInfrastructure.GetDatabaseConnection(); + await using DbConnection connection = registryInfrastructure.GetDatabaseConnection(); await connection.ExecuteAsync("DELETE FROM InfrastructureRegistry WHERE InfrastructureId = @InfrastructureId", new { InfrastructureId = infrastructureId.ToString() }); } public class FakeCleaner : IInfrastructureCleaner { - public static string Message = "[FakeCleaner] {0} with {1} for Id : {2}"; + public static readonly string Message = "[FakeCleaner] {0} with {1} for Id : {2}"; public Task CleanAfterCrash(EnvironmentId contextId, Guid infrastructureId, object? metadata = null) { Console.WriteLine(Message, metadata, contextId.Value, infrastructureId); return Task.CompletedTask; } } + [Cleaner(typeof(FakeCleaner))] - public class FakeInfrastructureWithCleaner : Infrastructure + public class FakeInfrastructureWithCleaner(EnvironmentId contextId, ITestLogger logger, IRegistry provider) + : Infrastructure(contextId, logger, provider) { - public FakeInfrastructureWithCleaner(EnvironmentId contextId, ITestLogger logger, IRegistry provider) : base(contextId, logger, provider) - { - } - - public override Task Destroy() - { - return Task.CompletedTask; - } + public override Task Destroy() => Task.CompletedTask; - public override Task Initialize() - { - return Task.CompletedTask; - } + public override Task Initialize() => Task.CompletedTask; } [Cleaner(typeof(FakeCleaner))] - public class FakeInfrastructure2WithCleaner : Infrastructure + public class FakeInfrastructure2WithCleaner(EnvironmentId contextId, ITestLogger logger, IRegistry provider) + : Infrastructure(contextId, logger, provider) { - public FakeInfrastructure2WithCleaner(EnvironmentId contextId, ITestLogger logger, IRegistry provider) : base(contextId, logger, provider) - { - } + public override Task Destroy() => Task.CompletedTask; - public override Task Destroy() - { - return Task.CompletedTask; - } - - public override Task Initialize() - { - return Task.CompletedTask; - } + public override Task Initialize() => Task.CompletedTask; } - public class FakeInfrastructureWithoutCleanerAttribute : Infrastructure + public class FakeInfrastructureWithoutCleanerAttribute( + EnvironmentId contextId, + ITestLogger logger, + IRegistry provider) + : Infrastructure(contextId, logger, provider) { - public FakeInfrastructureWithoutCleanerAttribute(EnvironmentId contextId, ITestLogger logger, IRegistry provider) : base(contextId, logger, provider) - { - } - public override Task Destroy() - { - return Task.CompletedTask; - } - public override Task Initialize() - { - return Task.CompletedTask; - } + public override Task Destroy() => Task.CompletedTask; + + public override Task Initialize() => Task.CompletedTask; } } public static class Act { - public static (Process? Process, StringBuilder StdoutBuilder) StartDoggyDog(int processId, string assembly, string connectionString, Guid environmentId) + public static (Process Process, StringBuilder StdoutBuilder) StartDoggyDog(int processId, string assembly, string connectionString, Guid environmentId) { - var doggyDogPath = Path.Combine(AppContext.BaseDirectory, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "DoggyDog.exe" : "DoggyDog"); - Process? doggyDogProcess = Process.Start(new ProcessStartInfo + string doggyDogPath = Path.Combine(AppContext.BaseDirectory, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "DoggyDog.exe" : "DoggyDog"); + var doggyDogProcess = Process.Start(new ProcessStartInfo { FileName = doggyDogPath, Arguments = $"--pid {processId} --assembly \"{assembly}\" --connectionString \"{connectionString}\" --environment {environmentId} --loglevel Debug", @@ -180,6 +146,11 @@ public static (Process? Process, StringBuilder StdoutBuilder) StartDoggyDog(int UseShellExecute = false, }); + if (doggyDogProcess == null) + { + Xunit.Assert.Fail("Fail to launch DoggyDog.exe"); + } + doggyDogProcess!.StandardInput.Close(); var stdoutBuilder = new StringBuilder(); @@ -211,8 +182,8 @@ public static async Task ShouldHaveNotFoundInfrastructureToClean(string stdout, } public static async Task ShouldHaveCleanedEntry(string stdout, SqliteInfrastructure registry, InfrastuctureRegistryEntry entry) { - using var connection = registry.GetDatabaseConnection(); - var count = await connection.ExecuteScalarAsync($"SELECT COUNT(*) FROM InfrastructureRegistry WHERE EnvironmentId = @EnvironmentId", new { EnvironmentId = entry.EnvironmentId }); + await using DbConnection connection = registry.GetDatabaseConnection(); + int count = await connection.ExecuteScalarAsync($"SELECT COUNT(*) FROM InfrastructureRegistry WHERE EnvironmentId = @EnvironmentId", new { EnvironmentId = entry.EnvironmentId }); count.Should().Be(0); @@ -225,8 +196,8 @@ public static async Task ShouldHaveCleanedEntry(string stdout, SqliteInfrastruct public static async Task ShouldHaveNotFoundCleanerAttribute(string stdout, SqliteInfrastructure registry, InfrastuctureRegistryEntry entry) { - using var connection = registry.GetDatabaseConnection(); - var count = await connection.ExecuteScalarAsync($"SELECT COUNT(*) FROM InfrastructureRegistry WHERE ProcessID = @ProcessId", new { ProcessId = entry.ProcessID }); + await using DbConnection connection = registry.GetDatabaseConnection(); + int count = await connection.ExecuteScalarAsync($"SELECT COUNT(*) FROM InfrastructureRegistry WHERE ProcessID = @ProcessId", new { ProcessId = entry.ProcessID }); count.Should().Be(1); diff --git a/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTests.cs b/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTests.cs index 336cc48..6849ab5 100644 --- a/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTests.cs +++ b/NotoriousTest.IntegrationTests/DoggyDog/DoggyDogTests.cs @@ -5,17 +5,17 @@ using System.Diagnostics; using System.Text; - +using NotoriousTest.Core.Registry; +using NotoriousTest.IntegrationTests.Infrastructure; +using NotoriousTest.IntegrationTests.SystemUnderTest; namespace NotoriousTest.IntegrationTests.DoggyDog { - public class DoggyDogTests : IntegrationTest + public class DoggyDogTests(NotoriousTestEnvironment environment) + : IntegrationTest(environment) { private const int FAKE_PROCESS_WAIT_TIME = 1; - public DoggyDogTests(DoggyDogEnvironment environment) : base(environment) - { - } [Fact] public async Task DoggyDog_Should_Do_Nothing_When_Process_Exit_Normally() @@ -23,22 +23,12 @@ public async Task DoggyDog_Should_Do_Nothing_When_Process_Exit_Normally() Guid environmentId = Guid.NewGuid(); Process? process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, timeToExit: FAKE_PROCESS_WAIT_TIME); - if (process == null) - { - Assert.Fail("Fail to launch cmd.exe"); - } - - var registry = CurrentEnvironment.GetInfrastructure(); - - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); - if (doggyDogProcess == null) - { - Assert.Fail("Fail to launch DoggyDog.exe"); - } + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveMonitoredProcess(stdout, process); await DoggyDogTestFramework.Assert.ShouldHaveReceivedExitSignal(stdout, environmentId); await DoggyDogTestFramework.Assert.ShouldHaveExitNormally(stdout, process, environmentId); @@ -48,28 +38,18 @@ public async Task DoggyDog_Should_Do_Nothing_When_Process_Exit_Normally() [Fact] public async Task DoggyDog_Should_Cleanup_Orphan_Infrastructures_When_Process_Exit_Abnormally() { - Guid environmentId = Guid.NewGuid(); - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); - - Process? process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); + var environmentId = Guid.NewGuid(); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); - if (process == null) - { - Assert.Fail("Fail to launch cmd.exe"); - } - var fakeEntry = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, process, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), environmentId); + Process process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); + InfrastuctureRegistryEntry fakeEntry = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, process, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), environmentId); - if (doggyDogProcess == null) - { - Assert.Fail("Fail to launch DoggyDog.exe"); - } + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveMonitoredProcess(stdout, process); await DoggyDogTestFramework.Assert.ShouldHaveFoundRegistryFile(stdout, registry.GetPath()); await DoggyDogTestFramework.Assert.ShouldHaveInitiatedRecovery(stdout, process, environmentId); @@ -81,16 +61,15 @@ public async Task DoggyDog_Should_Cleanup_Orphan_Infrastructures_When_Process_Ex [Fact] public async Task DoggyDog_Should_Exit_When_Monitored_Process_Not_Found() { - Guid environmentId = Guid.NewGuid(); - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); + var environmentId = Guid.NewGuid(); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); const int PROCESS_ID = 999999; - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(PROCESS_ID, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(PROCESS_ID, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveNotFoundProcess(stdout, PROCESS_ID); doggyDogProcess.ExitCode.Should().Be(0); } @@ -98,18 +77,16 @@ public async Task DoggyDog_Should_Exit_When_Monitored_Process_Not_Found() [Fact] public async Task DoggyDog_Should_Exit_When_Registry_File_Not_Found() { - Guid environmentId = Guid.NewGuid(); + var environmentId = Guid.NewGuid(); Process? process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); const string FAKE_REGISTRY_PATH = "C:\\fake\\path\\registry.db"; const string FAKE_CONNECTION_STRING = $"DataSource={FAKE_REGISTRY_PATH}"; - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, FAKE_CONNECTION_STRING, environmentId); + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, FAKE_CONNECTION_STRING, environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveNotFoundRegistryFile(stdout, FAKE_REGISTRY_PATH); doggyDogProcess.ExitCode.Should().Be(1); @@ -118,28 +95,17 @@ public async Task DoggyDog_Should_Exit_When_Registry_File_Not_Found() [Fact] public async Task DoggyDog_Should_Do_Nothing_When_Process_Crashes_With_No_Registered_Infrastructures() { - Guid environmentId = Guid.NewGuid(); - - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); + var environmentId = Guid.NewGuid(); - Process? process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); - if (process == null) - { - Assert.Fail("Fail to launch cmd.exe"); - } + Process process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); - - if (doggyDogProcess == null) - { - Assert.Fail("Fail to launch DoggyDog.exe"); - } + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveMonitoredProcess(stdout, process); await DoggyDogTestFramework.Assert.ShouldHaveFoundRegistryFile(stdout, registry.GetPath()); await DoggyDogTestFramework.Assert.ShouldHaveInitiatedRecovery(stdout, process, environmentId); @@ -150,28 +116,18 @@ public async Task DoggyDog_Should_Do_Nothing_When_Process_Crashes_With_No_Regist [Fact] public async Task DoggyDog_Should_Skip_Infrastructure_Without_Cleaner_Attribute_When_Process_Crashes() { - Guid environmentId = Guid.NewGuid(); - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); + var environmentId = Guid.NewGuid(); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); Process? process = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); - if (process == null) - { - Assert.Fail("Fail to launch cmd.exe"); - } - var fakeEntry = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, process, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithoutCleanerAttribute), environmentId); + InfrastuctureRegistryEntry fakeEntry = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, process, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithoutCleanerAttribute), environmentId); - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); - - if (doggyDogProcess == null) - { - Assert.Fail("Fail to launch DoggyDog.exe"); - } + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(process.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveMonitoredProcess(stdout, process); await DoggyDogTestFramework.Assert.ShouldHaveFoundRegistryFile(stdout, registry.GetPath()); await DoggyDogTestFramework.Assert.ShouldHaveInitiatedRecovery(stdout, process, environmentId); @@ -183,28 +139,18 @@ public async Task DoggyDog_Should_Skip_Infrastructure_Without_Cleaner_Attribute_ [Fact] public async Task DoggyDog_Should_Cleanup_Multiple_Orphan_Infrastructures_When_Process_Crashes() { - Guid environmentId = Guid.NewGuid(); - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); + var environmentId = Guid.NewGuid(); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); - Process? fakeProcess = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); + Process fakeProcess = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, exitCode: 1, timeToExit: FAKE_PROCESS_WAIT_TIME); - if (fakeProcess == null) - { - Assert.Fail("Fail to launch cmd.exe"); - } - var fakeEntry1 = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, fakeProcess, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), environmentId); - var fakeEntry2 = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, fakeProcess, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructure2WithCleaner), environmentId); + InfrastuctureRegistryEntry fakeEntry1 = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, fakeProcess, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), environmentId); + InfrastuctureRegistryEntry fakeEntry2 = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, fakeProcess, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructure2WithCleaner), environmentId); - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(fakeProcess.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); - - if (doggyDogProcess == null) - { - Assert.Fail("Fail to launch DoggyDog.exe"); - } + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(fakeProcess.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveMonitoredProcess(stdout, fakeProcess); await DoggyDogTestFramework.Assert.ShouldHaveFoundRegistryFile(stdout, registry.GetPath()); await DoggyDogTestFramework.Assert.ShouldHaveInitiatedRecovery(stdout, fakeProcess, environmentId); @@ -217,15 +163,14 @@ public async Task DoggyDog_Should_Cleanup_Multiple_Orphan_Infrastructures_When_P [Fact] public async Task DoggyDog_Should_Display_InfrastructureLifecycleEvents() { - Guid environmentId = Guid.NewGuid(); - var registry = CurrentEnvironment.GetInfrastructure(); - await DoggyDogTestFramework.Arrange.CreateRegistry(registry); + var environmentId = Guid.NewGuid(); + SqliteInfrastructure registry = CurrentEnvironment.GetInfrastructure(); - Process? fakeProcess = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, timeToExit: 999); - (Process? doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(fakeProcess.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); + Process fakeProcess = DoggyDogTestFramework.Arrange.StartFakeProcess(environmentId, timeToExit: 999); + (Process doggyDogProcess, StringBuilder stdoutBuilder) = DoggyDogTestFramework.Act.StartDoggyDog(fakeProcess.Id, typeof(DoggyDogTests).Assembly.Location, registry.GetDatabaseConnectionString(), environmentId); await Task.Delay(1000, TestContext.Current.CancellationToken); - var fakeEntry1 = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, fakeProcess, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), environmentId); + InfrastuctureRegistryEntry fakeEntry1 = await DoggyDogTestFramework.Arrange.CreateInfrastructureInRegistry(registry, fakeProcess, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), environmentId); await DoggyDogTestFramework.Assert.ShouldHaveCatchInitEvent(stdoutBuilder, typeof(DoggyDogTestFramework.Arrange.FakeInfrastructureWithCleaner), TestContext.Current.CancellationToken); await DoggyDogTestFramework.Arrange.TriggerInfrastructureReset(registry, fakeEntry1.InfrastructureId); @@ -236,7 +181,7 @@ public async Task DoggyDog_Should_Display_InfrastructureLifecycleEvents() fakeProcess.Kill(true); await doggyDogProcess.WaitForExitAsync(TestContext.Current.CancellationToken); - var stdout = stdoutBuilder.ToString(); + string stdout = stdoutBuilder.ToString(); await DoggyDogTestFramework.Assert.ShouldHaveMonitoredProcess(stdout, fakeProcess); await DoggyDogTestFramework.Assert.ShouldHaveFoundRegistryFile(stdout, registry.GetPath()); } diff --git a/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleEnvironment.cs b/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleEnvironment.cs deleted file mode 100644 index 8c7b25b..0000000 --- a/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleEnvironment.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Dapper; - -using NotoriousTest.Core; -using NotoriousTest.Core.Logger; -using NotoriousTest.Core.Registry; -using NotoriousTest.Core.Settings; -using NotoriousTest.Sqlite; - -using System.Reflection; - -using Xunit.Sdk; - -namespace NotoriousTest.IntegrationTests.Infrastructure; - -public class InfrastructureLifecycleEnvironment : XUnit.Environment -{ - public InfrastructureLifecycleEnvironment(IMessageSink sink) : base(sink) { } - - public override Assembly CurrentAssembly => Assembly.GetExecutingAssembly(); - - public override async Task ConfigureEnvironment() - { - AddInfrastructure(); - } -} - -public class RegistryDatabaseInfrastructure : SqliteInfrastructure -{ - public RegistryDatabaseInfrastructure(EnvironmentId contextId, ITestSettingsProvider settingsProvider, ITestLogger logger, IRegistry registry) - : base(contextId, settingsProvider, logger, registry) { } - - public override async Task Initialize() - { - await base.Initialize(); - using var connection = GetDatabaseConnection(); - await connection.ExecuteAsync(DoggyDogTestFramework.Arrange.ENSURE_REGISTRY); - } -} diff --git a/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleTests.cs b/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleTests.cs index 6804bff..9c78423 100644 --- a/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleTests.cs +++ b/NotoriousTest.IntegrationTests/Infrastructure/InfrastructureLifecycleTests.cs @@ -2,62 +2,62 @@ using NotoriousTest.Core; using NotoriousTest.Core.Logger; +using NotoriousTest.IntegrationTests.SystemUnderTest; using NotoriousTest.SqlLiteRegistry; using NotoriousTest.XUnit; namespace NotoriousTest.IntegrationTests.Infrastructure; -public class InfrastructureLifecycleTests : IntegrationTest +public class InfrastructureLifecycleTests(NotoriousTestEnvironment environment) + : IntegrationTest(environment) { - public InfrastructureLifecycleTests(InfrastructureLifecycleEnvironment environment) : base(environment) { } - [Fact] public async Task Initialize_Should_RegisterInfrastructureInRegistry() { EnvironmentId environmentId = Guid.NewGuid(); - var registryInfra = CurrentEnvironment.GetInfrastructure(); - await using var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration + NotoriousTestRegistryInfrastructure notoriousTestRegistryInfra = CurrentEnvironment.GetInfrastructure(); + var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration { - ConnectionString = registryInfra.GetDatabaseConnectionString() + ConnectionString = notoriousTestRegistryInfra.GetDatabaseConnectionString() }); await using var infrastructure = new TestFramework.Arrange.FakeInfrastructure(environmentId, A.Fake(), registry); await infrastructure.InitializeAsync(); - await TestFramework.Assert.InfrastructureShouldBeRegistered(registryInfra, infrastructure.Id); + await TestFramework.Assert.InfrastructureShouldBeRegistered(notoriousTestRegistryInfra, infrastructure.Id); } [Fact] public async Task Reset_Should_UpdateLastResetDateInRegistry() { EnvironmentId environmentId = Guid.NewGuid(); - var registryInfra = CurrentEnvironment.GetInfrastructure(); - await using var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration + NotoriousTestRegistryInfrastructure notoriousTestRegistryInfra = CurrentEnvironment.GetInfrastructure(); + var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration { - ConnectionString = registryInfra.GetDatabaseConnectionString() + ConnectionString = notoriousTestRegistryInfra.GetDatabaseConnectionString() }); await using var infrastructure = new TestFramework.Arrange.FakeInfrastructure(environmentId, A.Fake(), registry); await infrastructure.InitializeAsync(); await infrastructure.ResetAsync(); - await TestFramework.Assert.InfrastructureShouldHaveBeenReset(registryInfra, infrastructure.Id); + await TestFramework.Assert.InfrastructureShouldHaveBeenReset(notoriousTestRegistryInfra, infrastructure.Id); } [Fact] public async Task Destroy_Should_RemoveInfrastructureFromRegistry() { EnvironmentId environmentId = Guid.NewGuid(); - var registryInfra = CurrentEnvironment.GetInfrastructure(); - await using var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration + NotoriousTestRegistryInfrastructure notoriousTestRegistryInfra = CurrentEnvironment.GetInfrastructure(); + var registry = new SqliteRegistryProvider(new SqliteRegistryProviderConfiguration { - ConnectionString = registryInfra.GetDatabaseConnectionString() + ConnectionString = notoriousTestRegistryInfra.GetDatabaseConnectionString() }); var infrastructure = new TestFramework.Arrange.FakeInfrastructure(environmentId, A.Fake(), registry); await infrastructure.InitializeAsync(); await infrastructure.DestroyAsync(); - await TestFramework.Assert.InfrastructureShouldBeRemovedFromRegistry(registryInfra, infrastructure.Id); + await TestFramework.Assert.InfrastructureShouldBeRemovedFromRegistry(notoriousTestRegistryInfra, infrastructure.Id); } } diff --git a/NotoriousTest.IntegrationTests/Postgre/PostgreContainerInfrastructureTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/Postgre/PostgreContainerInfrastructureTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/Postgre/PostgreContainerInfrastructureTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/Postgre/PostgreContainerInfrastructureTests.cs diff --git a/NotoriousTest.IntegrationTests/Postgre/PostgreInfrastructureEnvironment.cs b/NotoriousTest.IntegrationTests/Infrastructures/Postgre/PostgreInfrastructureEnvironment.cs similarity index 100% rename from NotoriousTest.IntegrationTests/Postgre/PostgreInfrastructureEnvironment.cs rename to NotoriousTest.IntegrationTests/Infrastructures/Postgre/PostgreInfrastructureEnvironment.cs diff --git a/NotoriousTest.IntegrationTests/Postgre/PostgreInfrastructureTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/Postgre/PostgreInfrastructureTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/Postgre/PostgreInfrastructureTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/Postgre/PostgreInfrastructureTests.cs diff --git a/NotoriousTest.IntegrationTests/SqlServer/SqlServerContainerInfrastructuresTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/SqlServer/SqlServerContainerInfrastructuresTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/SqlServer/SqlServerContainerInfrastructuresTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/SqlServer/SqlServerContainerInfrastructuresTests.cs diff --git a/NotoriousTest.IntegrationTests/SqlServer/SqlServerInfrastructureEnvironment.cs b/NotoriousTest.IntegrationTests/Infrastructures/SqlServer/SqlServerInfrastructureEnvironment.cs similarity index 100% rename from NotoriousTest.IntegrationTests/SqlServer/SqlServerInfrastructureEnvironment.cs rename to NotoriousTest.IntegrationTests/Infrastructures/SqlServer/SqlServerInfrastructureEnvironment.cs diff --git a/NotoriousTest.IntegrationTests/SqlServer/SqlServerInfrastructureTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/SqlServer/SqlServerInfrastructureTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/SqlServer/SqlServerInfrastructureTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/SqlServer/SqlServerInfrastructureTests.cs diff --git a/NotoriousTest.IntegrationTests/Sqlite/SqliteInfrastructureTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/Sqlite/SqliteInfrastructureTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/Sqlite/SqliteInfrastructureTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/Sqlite/SqliteInfrastructureTests.cs diff --git a/NotoriousTest.IntegrationTests/TestContainers/ContainerInfrastructureTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/TestContainers/ContainerInfrastructureTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/TestContainers/ContainerInfrastructureTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/TestContainers/ContainerInfrastructureTests.cs diff --git a/NotoriousTest.IntegrationTests/WebApplication/TestWebApplication.cs b/NotoriousTest.IntegrationTests/Infrastructures/WebApplication/TestWebApplication.cs similarity index 100% rename from NotoriousTest.IntegrationTests/WebApplication/TestWebApplication.cs rename to NotoriousTest.IntegrationTests/Infrastructures/WebApplication/TestWebApplication.cs diff --git a/NotoriousTest.IntegrationTests/WebApplication/WebApplicationInfrastructureTests.cs b/NotoriousTest.IntegrationTests/Infrastructures/WebApplication/WebApplicationInfrastructureTests.cs similarity index 100% rename from NotoriousTest.IntegrationTests/WebApplication/WebApplicationInfrastructureTests.cs rename to NotoriousTest.IntegrationTests/Infrastructures/WebApplication/WebApplicationInfrastructureTests.cs diff --git a/NotoriousTest.IntegrationTests/NotoriousTest.IntegrationTests.csproj b/NotoriousTest.IntegrationTests/NotoriousTest.IntegrationTests.csproj index 5c3ca5c..a70eecf 100644 --- a/NotoriousTest.IntegrationTests/NotoriousTest.IntegrationTests.csproj +++ b/NotoriousTest.IntegrationTests/NotoriousTest.IntegrationTests.csproj @@ -40,4 +40,8 @@ + + + + \ No newline at end of file diff --git a/NotoriousTest.IntegrationTests/SystemUnderTest/NotoriousTestEnvironment.cs b/NotoriousTest.IntegrationTests/SystemUnderTest/NotoriousTestEnvironment.cs new file mode 100644 index 0000000..d995b00 --- /dev/null +++ b/NotoriousTest.IntegrationTests/SystemUnderTest/NotoriousTestEnvironment.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using NotoriousTest.IntegrationTests.Infrastructure; +using Xunit.Sdk; + +namespace NotoriousTest.IntegrationTests.SystemUnderTest; + +public class NotoriousTestEnvironment(IMessageSink sink) : XUnit.Environment(sink) +{ + public override Assembly CurrentAssembly => Assembly.GetExecutingAssembly(); + + public override Task ConfigureEnvironment() => Task.FromResult(AddInfrastructure()); +} diff --git a/NotoriousTest.IntegrationTests/SystemUnderTest/NotoriousTestRegistryInfrastructure.cs b/NotoriousTest.IntegrationTests/SystemUnderTest/NotoriousTestRegistryInfrastructure.cs new file mode 100644 index 0000000..8f102c5 --- /dev/null +++ b/NotoriousTest.IntegrationTests/SystemUnderTest/NotoriousTestRegistryInfrastructure.cs @@ -0,0 +1,38 @@ +using System.Data.Common; +using Dapper; +using NotoriousTest.Core; +using NotoriousTest.Core.Logger; +using NotoriousTest.Core.Registry; +using NotoriousTest.Core.Settings; +using NotoriousTest.Sqlite; + +namespace NotoriousTest.IntegrationTests.SystemUnderTest; + +public class NotoriousTestRegistryInfrastructure( + EnvironmentId contextId, + ITestSettingsProvider settingsProvider, + ITestLogger logger, + IRegistry registry) + : SqliteInfrastructure(contextId, settingsProvider, logger, registry) +{ + public override async Task Initialize() + { + await base.Initialize(); + await using DbConnection connection = GetDatabaseConnection(); + await connection.ExecuteAsync(ENSURE_REGISTRY); + } + + private const string ENSURE_REGISTRY = @$" + CREATE TABLE IF NOT EXISTS InfrastructureRegistry( + InfrastructureId TEXT PRIMARY KEY, + InfrastructureType TEXT NOT NULL, + EnvironmentId TEXT NOT NULL, + ProcessID TEXT NOT NULL, + Metadata TEXT, + MetadataType TEXT, + CreationDate TEXT NOT NULL, + UpdateDate TEXT NOT NULL, + LastResetDate TEXT + ) + "; +} diff --git a/NotoriousTest.IntegrationTests/testsettings.json b/NotoriousTest.IntegrationTests/testsettings.json index fb325d7..b65defc 100644 --- a/NotoriousTest.IntegrationTests/testsettings.json +++ b/NotoriousTest.IntegrationTests/testsettings.json @@ -2,13 +2,13 @@ "SqliteInfrastructure": { "ConnectionString": "Data Source=test.db" }, - "RegistryDatabaseInfrastructure": { + "NotoriousTestRegistryInfrastructure": { "ConnectionString": "Data Source=registry-test.db" }, "Environment": { "DisableWatchdog": false }, "Watchdog": { - "ManualLaunch": false + "ManualLaunch": false } } diff --git a/NotoriousTest.MSTest/Environment.cs b/NotoriousTest.MSTest/Environment.cs index 789ac9f..272aabe 100644 --- a/NotoriousTest.MSTest/Environment.cs +++ b/NotoriousTest.MSTest/Environment.cs @@ -18,7 +18,7 @@ protected Environment(TestContext testContext) _testContext = testContext; } - public override void ConfigureInfrastructureServices(IServiceCollection collection) + protected override void ConfigureInfrastructureServices(IServiceCollection collection) { base.ConfigureInfrastructureServices(collection); diff --git a/NotoriousTest.NUnit/Environment.cs b/NotoriousTest.NUnit/Environment.cs index 301fad5..435cd25 100644 --- a/NotoriousTest.NUnit/Environment.cs +++ b/NotoriousTest.NUnit/Environment.cs @@ -10,7 +10,7 @@ namespace NotoriousTest.NUnit { public abstract class Environment : EnvironmentBase { - public override void ConfigureInfrastructureServices(IServiceCollection collection) + protected override void ConfigureInfrastructureServices(IServiceCollection collection) { base.ConfigureInfrastructureServices(collection); diff --git a/NotoriousTest.PostgreSql/PostgreContainerInfrastructure.cs b/NotoriousTest.PostgreSql/PostgreContainerInfrastructure.cs index 8e9b49c..df70e11 100644 --- a/NotoriousTest.PostgreSql/PostgreContainerInfrastructure.cs +++ b/NotoriousTest.PostgreSql/PostgreContainerInfrastructure.cs @@ -37,35 +37,18 @@ public override async Task Initialize() public class PostgreContainerInfrastructure : DockerDatabaseInfrastructure { - public string[] SchemasToInclude { get; init; } = []; - public string[] SchemasToExclude { get; init; } = []; - public PostgreContainerInfrastructure(Guid contextId, ITestLogger logger, IRegistry registry) : base(contextId, logger, registry) { Container = ConfigureSqlContainer(new PostgreSqlBuilder()).Build(); - EnsureExtension(new RespawnExtension(() => new RespawnerOptions() - { - TablesToIgnore = TableToIgnore.Select(tti => new Table(tti)).ToArray(), - TablesToInclude = TableToInclude.Select(tti => new Table(tti)).ToArray(), - SchemasToExclude = SchemasToExclude, - SchemasToInclude = SchemasToInclude, - DbAdapter = DbAdapter.Postgres - })); } - public override DbConnection GetConnection(string connectionString) - { - return new NpgsqlConnection(connectionString); - } + public override DbConnection GetConnection(string connectionString) => new NpgsqlConnection(connectionString); - protected virtual PostgreSqlBuilder ConfigureSqlContainer(PostgreSqlBuilder builder) - { - return builder; - } + protected virtual PostgreSqlBuilder ConfigureSqlContainer(PostgreSqlBuilder builder) => builder; public override string GetDatabaseConnectionString() { - NpgsqlConnectionStringBuilder connectionString = new NpgsqlConnectionStringBuilder(Container.GetConnectionString()); + var connectionString = new NpgsqlConnectionStringBuilder(Container.GetConnectionString()); if (!string.IsNullOrEmpty(FullDbName)) { connectionString.Database = FullDbName; @@ -76,7 +59,7 @@ public override string GetDatabaseConnectionString() protected override async Task CreateDatabase(DbConnection sqlConnection) { - using (DbCommand command = sqlConnection.CreateCommand()) + await using (DbCommand command = sqlConnection.CreateCommand()) { command.CommandText = $"CREATE DATABASE \"{FullDbName}\""; await command.ExecuteNonQueryAsync(); @@ -84,4 +67,4 @@ protected override async Task CreateDatabase(DbConnection sqlConnection) } -} \ No newline at end of file +} diff --git a/NotoriousTest.PostgreSql/PostgreInfrastructure.cs b/NotoriousTest.PostgreSql/PostgreInfrastructure.cs index 404fdba..265237a 100644 --- a/NotoriousTest.PostgreSql/PostgreInfrastructure.cs +++ b/NotoriousTest.PostgreSql/PostgreInfrastructure.cs @@ -8,9 +8,6 @@ using Npgsql; -using Respawn; -using Respawn.Graph; - using System.Data.Common; namespace NotoriousTest.PostgreSql @@ -38,29 +35,13 @@ public override async Task Initialize() [Cleaner(typeof(PostgreInfrastructureCleaner))] public class PostgreInfrastructure : ExternalDatabaseInfrastructure where TSettings : DatabaseSettings, new() { - public string[] SchemasToInclude { get; init; } = []; - public string[] SchemasToExclude { get; init; } = []; - - public PostgreInfrastructure(EnvironmentId contextId, ITestSettingsProvider settingsProvider, ITestLogger logger, IRegistry registry) : base(contextId, settingsProvider, logger, registry) - { - EnsureExtension(new RespawnExtension(() => new RespawnerOptions() - { - TablesToIgnore = TableToIgnore.Select(tti => new Table(tti)).ToArray(), - TablesToInclude = TableToInclude.Select(tti => new Table(tti)).ToArray(), - SchemasToExclude = SchemasToExclude, - SchemasToInclude = SchemasToInclude, - DbAdapter = DbAdapter.Postgres - })); - } + public PostgreInfrastructure(EnvironmentId contextId, ITestSettingsProvider settingsProvider, ITestLogger logger, IRegistry registry) : base(contextId, settingsProvider, logger, registry) {} - public override DbConnection GetConnection(string connectionString) - { - return new NpgsqlConnection(connectionString); - } + public override DbConnection GetConnection(string connectionString) => new NpgsqlConnection(connectionString); public override string GetDatabaseConnectionString() { - NpgsqlConnectionStringBuilder connectionString = new NpgsqlConnectionStringBuilder(Settings.ConnectionString); + var connectionString = new NpgsqlConnectionStringBuilder(Settings.ConnectionString); if (!string.IsNullOrEmpty(FullDbName)) { connectionString.Database = FullDbName; @@ -71,7 +52,7 @@ public override string GetDatabaseConnectionString() protected override async Task CreateDatabase(DbConnection sqlConnection) { - using (DbCommand command = sqlConnection.CreateCommand()) + await using (DbCommand command = sqlConnection.CreateCommand()) { command.CommandText = $"CREATE DATABASE \"{FullDbName}\""; await command.ExecuteNonQueryAsync(); @@ -80,10 +61,10 @@ protected override async Task CreateDatabase(DbConnection sqlConnection) protected override async Task DropDatabase(DbConnection sqlConnection) { - using var connection = (NpgsqlConnection)GetDatabaseConnection(); + await using var connection = (NpgsqlConnection)GetDatabaseConnection(); NpgsqlConnection.ClearPool(connection); - using (DbCommand command = sqlConnection.CreateCommand()) + await using (DbCommand command = sqlConnection.CreateCommand()) { command.CommandText = $"DROP DATABASE \"{FullDbName}\""; await command.ExecuteNonQueryAsync(); diff --git a/NotoriousTest.SqlLiteRegistry/SqliteRegistryProvider.cs b/NotoriousTest.SqlLiteRegistry/SqliteRegistryProvider.cs index bd01d75..76ce971 100644 --- a/NotoriousTest.SqlLiteRegistry/SqliteRegistryProvider.cs +++ b/NotoriousTest.SqlLiteRegistry/SqliteRegistryProvider.cs @@ -6,7 +6,7 @@ using NotoriousTest.Core.Registry; namespace NotoriousTest.SqlLiteRegistry { - internal partial class SqliteRegistryProvider : IRegistry, IAsyncDisposable + internal partial class SqliteRegistryProvider : IRegistry { private readonly SqliteRegistryProviderConfiguration _configuration; public event Action OnInfrastructureReset; @@ -18,24 +18,21 @@ public SqliteRegistryProvider(SqliteRegistryProviderConfiguration configuration) _configuration = configuration; } - private SqliteConnection _connection; - private SqliteConnectionStringBuilder ConnectionString => new SqliteConnectionStringBuilder(_configuration.ConnectionString); - protected SqliteConnection Connection + private SqliteConnectionStringBuilder ConnectionString => new(_configuration.ConnectionString); + + private SqliteConnection Connection { get { - if (_connection == null) - { - _connection = new SqliteConnection(ConnectionString.ToString()); - } + var connection = new SqliteConnection(ConnectionString.ToString()); - if (_connection.State != System.Data.ConnectionState.Open) + if (connection.State != System.Data.ConnectionState.Open) { - _connection.Open(); + connection.Open(); } - _connection.Execute("PRAGMA SYNCHRONOUS=NORMAL;PRAGMA JOURNAL_MODE=WAL;"); - return _connection; + connection.Execute("PRAGMA SYNCHRONOUS=NORMAL;PRAGMA JOURNAL_MODE=WAL;"); + return connection; } } @@ -47,48 +44,54 @@ public async Task Ensure() { Directory.CreateDirectory(directory); } - await Connection.ExecuteAsync(ENSURE_REGISTRY); + + await using SqliteConnection connection = Connection; + + await connection.ExecuteAsync(ENSURE_REGISTRY); } public async Task Register(InfrastuctureRegistryEntry entry) { - - InfrastructureRegistryEntryEntity entity = await Connection.QuerySingleAsync(REGISTER_INFRASTRUCTURE, InfrastructureRegistryEntryEntity.FromDomain(entry)); + await using SqliteConnection connection = Connection; + InfrastructureRegistryEntryEntity entity = await connection.QuerySingleAsync(REGISTER_INFRASTRUCTURE, InfrastructureRegistryEntryEntity.FromDomain(entry)); return entity.ToDomain(); } - public async ValueTask DisposeAsync() - { - await Connection.DisposeAsync(); - } - public async Task Remove(Guid id) { - int deletedRows = await Connection.ExecuteAsync(REMOVE_INFRASTRUCTURE, new { InfrastructureId = id.ToString() }); + await using SqliteConnection connection = Connection; + + int deletedRows = await connection.ExecuteAsync(REMOVE_INFRASTRUCTURE, new { InfrastructureId = id.ToString() }); return deletedRows > 0; } public async Task NotifyReset(Guid id) { - await Connection.ExecuteAsync(UPDATE_INFRASTRUCTURE_RESET_DATE, new { InfrastructureId = id.ToString() }); + await using SqliteConnection connection = Connection; + + await connection.ExecuteAsync(UPDATE_INFRASTRUCTURE_RESET_DATE, new { InfrastructureId = id.ToString() }); } public async Task> GetByProcessId(int processId) { - IEnumerable entries = await Connection.QueryAsync(GET_BY_PROCESS_ID, new { ProcessID = processId }); + await using SqliteConnection connection = Connection; + + IEnumerable entries = await connection.QueryAsync(GET_BY_PROCESS_ID, new { ProcessID = processId }); return entries.Select(entry => entry.ToDomain()); } public async Task> GetByEnvironmentId(EnvironmentId environmentId) { - IEnumerable entries = await Connection.QueryAsync(GET_BY_ENVIRONMENT_ID, new { EnvironmentId = environmentId.Value.ToString() }); + await using SqliteConnection connection = Connection; + + IEnumerable entries = await connection.QueryAsync(GET_BY_ENVIRONMENT_ID, new { EnvironmentId = environmentId.Value.ToString() }); return entries.Select(entry => entry.ToDomain()); } public async Task Watch(EnvironmentId environmentId, CancellationToken ct) { - Dictionary snapshot = (await GetByEnvironmentId(environmentId)).ToDictionary(x => x.InfrastructureId); + var snapshot = (await GetByEnvironmentId(environmentId)).ToDictionary(x => x.InfrastructureId); do { diff --git a/NotoriousTest.SqlServer/SqlServerContainerInfrastructure.cs b/NotoriousTest.SqlServer/SqlServerContainerInfrastructure.cs index 35e2025..3f5cc1d 100644 --- a/NotoriousTest.SqlServer/SqlServerContainerInfrastructure.cs +++ b/NotoriousTest.SqlServer/SqlServerContainerInfrastructure.cs @@ -37,35 +37,18 @@ public override async Task Initialize() public class SqlServerContainerInfrastructure : DockerDatabaseInfrastructure { - public string[] SchemasToInclude { get; init; } = []; - public string[] SchemasToExclude { get; init; } = []; - public SqlServerContainerInfrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry registry) : base(contextId, logger, registry) { Container = ConfigureSqlContainer(new MsSqlBuilder()).Build(); - EnsureExtension(new RespawnExtension(() => new RespawnerOptions() - { - TablesToIgnore = TableToIgnore.Select(tti => new Table(tti)).ToArray(), - TablesToInclude = TableToInclude.Select(tti => new Table(tti)).ToArray(), - SchemasToExclude = SchemasToExclude, - SchemasToInclude = SchemasToInclude, - DbAdapter = DbAdapter.SqlServer - })); } - protected virtual MsSqlBuilder ConfigureSqlContainer(MsSqlBuilder builder) - { - return builder; - } + private MsSqlBuilder ConfigureSqlContainer(MsSqlBuilder builder) => builder; - public override DbConnection GetConnection(string connectionString) - { - return new SqlConnection(connectionString); - } + public override DbConnection GetConnection(string connectionString) => new SqlConnection(connectionString); public override string GetDatabaseConnectionString() { - SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder(Container.GetConnectionString()); + var connectionString = new SqlConnectionStringBuilder(Container.GetConnectionString()); if (!string.IsNullOrEmpty(FullDbName)) { connectionString.InitialCatalog = FullDbName; @@ -76,8 +59,7 @@ public override string GetDatabaseConnectionString() protected override async Task CreateDatabase(DbConnection sqlConnection) { - - using (DbCommand command = sqlConnection.CreateCommand()) + await using (DbCommand command = sqlConnection.CreateCommand()) { command.CommandText = $"CREATE DATABASE [{FullDbName}]"; await command.ExecuteNonQueryAsync(); diff --git a/NotoriousTest.SqlServer/SqlServerDatabaseCleaner.cs b/NotoriousTest.SqlServer/SqlServerDatabaseCleaner.cs index c7fcf52..d412741 100644 --- a/NotoriousTest.SqlServer/SqlServerDatabaseCleaner.cs +++ b/NotoriousTest.SqlServer/SqlServerDatabaseCleaner.cs @@ -8,12 +8,13 @@ namespace NotoriousTest.SqlServer { public class SqlServerInfrastructureCleaner : IInfrastructureCleaner { - public async Task CleanAfterCrash(EnvironmentId contextId, Guid infrastructureId, DatabaseMetadata metadata = null) + public async Task CleanAfterCrash(EnvironmentId contextId, Guid infrastructureId, DatabaseMetadata? metadata = null) { - using var connection = new SqlConnection(metadata.ServerConnectionString); + if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + await using var connection = new SqlConnection(metadata.ServerConnectionString); await connection.OpenAsync(); - var command = connection.CreateCommand(); + SqlCommand? command = connection.CreateCommand(); command.CommandText = $"DROP DATABASE [{metadata.DatabaseName}]"; await command.ExecuteNonQueryAsync(); } diff --git a/NotoriousTest.SqlServer/SqlServerInfrastructure.cs b/NotoriousTest.SqlServer/SqlServerInfrastructure.cs index ac9d5be..1fd6436 100644 --- a/NotoriousTest.SqlServer/SqlServerInfrastructure.cs +++ b/NotoriousTest.SqlServer/SqlServerInfrastructure.cs @@ -17,7 +17,10 @@ namespace NotoriousTest.SqlServer { public class SqlServerInfrastructure : SqlServerInfrastructure { - public SqlServerInfrastructure(EnvironmentId contextId, ITestSettingsProvider settingsProvider, ITestLogger logger, IRegistry registry) : base(contextId, settingsProvider, logger, registry) + public SqlServerInfrastructure(EnvironmentId contextId, + ITestSettingsProvider settingsProvider, + ITestLogger logger, + IRegistry registry) : base(contextId, settingsProvider, logger, registry) { } @@ -38,29 +41,15 @@ public override async Task Initialize() [Cleaner(typeof(SqlServerInfrastructureCleaner))] public class SqlServerInfrastructure : ExternalDatabaseInfrastructure where TSettings : DatabaseSettings, new() { - public string[] SchemasToInclude { get; init; } = []; - public string[] SchemasToExclude { get; init; } = []; - public SqlServerInfrastructure(EnvironmentId contextId, ITestSettingsProvider testSettingsProvider, ITestLogger logger, IRegistry registry) : base(contextId, testSettingsProvider, logger, registry) { - EnsureExtension(new RespawnExtension(() => new RespawnerOptions() - { - TablesToIgnore = TableToIgnore.Select(tti => new Table(tti)).ToArray(), - TablesToInclude = TableToInclude.Select(tti => new Table(tti)).ToArray(), - SchemasToExclude = SchemasToExclude, - SchemasToInclude = SchemasToInclude, - DbAdapter = DbAdapter.SqlServer - })); } - public override DbConnection GetConnection(string connectionString) - { - return new SqlConnection(connectionString); - } + public override DbConnection GetConnection(string connectionString) => new SqlConnection(connectionString); public override string GetDatabaseConnectionString() { - SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder(Settings.ConnectionString) + var connectionString = new SqlConnectionStringBuilder(Settings.ConnectionString) { InitialCatalog = FullDbName }; @@ -70,23 +59,18 @@ public override string GetDatabaseConnectionString() protected override async Task CreateDatabase(DbConnection sqlConnection) { - using (DbCommand command = sqlConnection.CreateCommand()) - { - command.CommandText = $"CREATE DATABASE [{FullDbName}]"; - await command.ExecuteNonQueryAsync(); - } + await using DbCommand command = sqlConnection.CreateCommand(); + command.CommandText = $"CREATE DATABASE [{FullDbName}]"; + await command.ExecuteNonQueryAsync(); } protected override async Task DropDatabase(DbConnection sqlConnection) { SqlConnection.ClearPool((SqlConnection)GetDatabaseConnection()); - using (DbCommand command = sqlConnection.CreateCommand()) - { - command.CommandText = $@" - DROP DATABASE [{FullDbName}]"; - await command.ExecuteNonQueryAsync(); - } + await using DbCommand command = sqlConnection.CreateCommand(); + command.CommandText = $@"DROP DATABASE [{FullDbName}]"; + await command.ExecuteNonQueryAsync(); } } } diff --git a/NotoriousTest.Sqlite/SqliteInfrastructure.cs b/NotoriousTest.Sqlite/SqliteInfrastructure.cs index 3b143fb..7fce4a0 100644 --- a/NotoriousTest.Sqlite/SqliteInfrastructure.cs +++ b/NotoriousTest.Sqlite/SqliteInfrastructure.cs @@ -41,23 +41,12 @@ public override async Task Initialize() private string _connectionString; public SqliteInfrastructure(EnvironmentId contextId, ITestSettingsProvider settingsProvider, ITestLogger logger, IRegistry registry) : base(contextId, settingsProvider, logger, registry) { - EnsureExtension(new RespawnExtension(() => new RespawnerOptions() - { - TablesToIgnore = TableToIgnore.Select(tti => new Table(tti)).ToArray(), - TablesToInclude = TableToInclude.Select(tti => new Table(tti)).ToArray(), - DbAdapter = DbAdapter.Sqlite - })); } - public override DbConnection GetConnection(string connectionString) - { - return new SqliteConnection(connectionString); - } + public override DbConnection GetConnection(string connectionString) => new SqliteConnection(connectionString); + + public string GetPath() => new SqliteConnectionStringBuilder(GetServerConnectionString()).DataSource; - public string GetPath() - { - return new SqliteConnectionStringBuilder(GetServerConnectionString()).DataSource; - } public override string GetServerConnectionString() { if (string.IsNullOrWhiteSpace(_connectionString)) @@ -75,20 +64,17 @@ public override string GetServerConnectionString() return _connectionString; } - public override string GetDatabaseConnectionString() - { + public override string GetDatabaseConnectionString() => // For SQLite, the server connection string and the database connection string are the same, since SQLite is a file-based database. - return GetServerConnectionString(); - } + GetServerConnectionString(); - protected override async Task CreateDatabase(DbConnection sqliteConnection) - { - // Since the connection is already opened, nothing to do. The database file will be created automatically when the connection is opened if it does not exist. - } + protected override Task CreateDatabase(DbConnection sqliteConnection) => Task.CompletedTask; + // Since the connection is already opened, nothing to do. The database file will be created automatically when the connection is opened if it does not exist. protected override async Task DropDatabase(DbConnection sqlConnection) { await sqlConnection.CloseAsync(); + await sqlConnection.DisposeAsync(); SqliteConnection.ClearAllPools(); File.Delete(sqlConnection.DataSource); } diff --git a/NotoriousTest.Sqlite/SqliteInfrastructureCleaner.cs b/NotoriousTest.Sqlite/SqliteInfrastructureCleaner.cs index 9c2d215..5318298 100644 --- a/NotoriousTest.Sqlite/SqliteInfrastructureCleaner.cs +++ b/NotoriousTest.Sqlite/SqliteInfrastructureCleaner.cs @@ -8,9 +8,10 @@ namespace NotoriousTest.Sqlite { public class SqliteInfrastructureCleaner : IInfrastructureCleaner { - public async Task CleanAfterCrash(EnvironmentId contextId, Guid infrastructureId, DatabaseMetadata metadata = null) + public async Task CleanAfterCrash(EnvironmentId contextId, Guid infrastructureId, DatabaseMetadata? metadata = null) { - File.Delete(new SqliteConnectionStringBuilder(metadata.ServerConnectionString).DataSource); + if (metadata != null) + File.Delete(new SqliteConnectionStringBuilder(metadata.ServerConnectionString).DataSource); } } } diff --git a/NotoriousTest.TUnit/Environment.cs b/NotoriousTest.TUnit/Environment.cs index 2b72444..9ccd56d 100644 --- a/NotoriousTest.TUnit/Environment.cs +++ b/NotoriousTest.TUnit/Environment.cs @@ -10,7 +10,7 @@ namespace NotoriousTest.TUnit { public abstract class Environment : EnvironmentBase, IAsyncInitializer, IAsyncDisposable { - public override void ConfigureInfrastructureServices(IServiceCollection collection) + protected override void ConfigureInfrastructureServices(IServiceCollection collection) { base.ConfigureInfrastructureServices(collection); diff --git a/NotoriousTest.UnitTests/Core/TypedExtensionDispatchTests.cs b/NotoriousTest.UnitTests/Core/TypedExtensionDispatchTests.cs deleted file mode 100644 index c7e6215..0000000 --- a/NotoriousTest.UnitTests/Core/TypedExtensionDispatchTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using AwesomeAssertions; - -using FakeItEasy; - -using NotoriousTest.Core.Logger; -using NotoriousTest.Core.Registry; -using NotoriousTest.Core.Watchdog; -using NotoriousTest.UnitTests.Stubs; - -namespace NotoriousTest.UnitTests.Core; - -public class TypedExtensionDispatchTests -{ - - private readonly ITestLogger _testLogger; - private readonly IRegistry _registry; - private readonly IWatchDog _watchDog; - - public TypedExtensionDispatchTests() - { - _testLogger = A.Fake(); - _registry = A.Fake(); - _watchDog = A.Fake(); - } - - [Fact] - public async Task TypedExtension_Should_InvokeTypedHooks_WhenCalledWithMatchingType() - { - var infra1 = new InfrastructureStub(_testLogger, _registry); - InfrastructureStub? actualInfra = null; - ExtensionStub extension = new() - { - OnBeforeInitializeAction = (infra) => - { - actualInfra = infra; - } - }; - infra1.EnsureExtension(extension); - - await infra1.InitializeAsync(); - - actualInfra.Should().NotBeNull().And.Be(infra1); - } - - [Fact] - public async Task TypedExtension_Should_NotInvokeTypedHooks_WhenCalledWithNonMatchingType() - { - var infra1 = new InfrastructureStub(_testLogger, _registry); - - ExtensionStub extension = new(); - infra1.EnsureExtension(extension); - - var act = () => infra1.InitializeAsync(); - await act.Should().ThrowAsync(); - } - - class DifferentInfrastructureStub : NotoriousTest.Core.Infrastructures.Infrastructure - { - public DifferentInfrastructureStub(ITestLogger logger, IRegistry registry) : base(Guid.NewGuid(), logger, registry) - { - } - - public override Task Destroy() - { - return Task.CompletedTask; - } - - public override Task Initialize() - { - return Task.CompletedTask; - } - } -} diff --git a/NotoriousTest.UnitTests/Infrastructure/InfrastructureExtensionsTests.cs b/NotoriousTest.UnitTests/Infrastructure/InfrastructureExtensionsTests.cs deleted file mode 100644 index 91c4d41..0000000 --- a/NotoriousTest.UnitTests/Infrastructure/InfrastructureExtensionsTests.cs +++ /dev/null @@ -1,170 +0,0 @@ -using AwesomeAssertions; - -using FakeItEasy; - -using NotoriousTest.Core.Logger; -using NotoriousTest.Core.Registry; -using NotoriousTest.UnitTests.Stubs; - -namespace NotoriousTest.UnitTests.Infrastructure; - -public class InfrastructureExtensionsTests -{ - - private readonly ITestLogger _testLogger; - private readonly IRegistry _registry; - - public InfrastructureExtensionsTests() - { - _testLogger = A.Fake(); - _registry = A.Fake(); - } - - [Fact] - public async Task Extensions_Should_CallOnBeforeInitialize_BeforeInitialize() - { - List order = new(); - - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry) - { - OnInitialize = async () => - { - order.Add(2); - } - }; - var extension = infrastructure.EnsureExtension>(); - extension.OnBeforeInitializeAction = (_) => - { - order.Add(1); - }; - await infrastructure.InitializeAsync(); - - order.Should().Equal(1, 2); - } - - [Fact] - public async Task Extensions_Should_CallOnAfterInitialize_AfterInitialize() - { - List order = new(); - - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry) - { - OnInitialize = async () => - { - order.Add(1); - } - }; - var extension = infrastructure.EnsureExtension>(); - extension.OnAfterInitializeAction = (_) => - { - order.Add(2); - }; - - await infrastructure.InitializeAsync(); - - order.Should().Equal(1, 2); - } - - [Fact] - public async Task Extensions_Should_CallOnBeforeReset_BeforeReset() - { - List order = new(); - - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry) - { - OnReset = async () => - { - order.Add(2); - } - }; - var extension = infrastructure.EnsureExtension>(); - extension.OnBeforeResetAction = (_) => - { - order.Add(1); - }; - - await infrastructure.ResetAsync(); - - order.Should().Equal(1, 2); - } - - [Fact] - public async Task Extensions_Should_CallOnAfterReset_AfterReset() - { - List order = new(); - - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry) - { - OnReset = async () => - { - order.Add(1); - } - }; - var extension = infrastructure.EnsureExtension>(); - extension.OnAfterResetAction = (_) => - { - order.Add(2); - }; - - await infrastructure.ResetAsync(); - - order.Should().Equal(1, 2); - } - - [Fact] - public async Task Extensions_Should_CallOnBeforeDestroy_BeforeDestroy() - { - List order = new(); - - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry) - { - OnDestroy = async () => - { - order.Add(2); - } - }; - - var extension = infrastructure.EnsureExtension>(); - extension.OnBeforeDestroyAction = (_) => - { - order.Add(1); - }; - - await infrastructure.DestroyAsync(); - - order.Should().Equal(1, 2); - } - - [Fact] - public async Task Extensions_Should_CallOnAfterDestroy_AfterDestroy() - { - List order = new(); - - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry) - { - OnDestroy = async () => - { - order.Add(1); - } - }; - - var extension = infrastructure.EnsureExtension>(); - extension.OnAfterDestroyAction = (_) => - { - order.Add(2); - }; - - await infrastructure.DestroyAsync(); - - order.Should().Equal(1, 2); - } - - [Fact] - public void EnsureExtension_Should_ReturnSameInstance_WhenExtensionAlreadyExists() - { - InfrastructureStub infrastructure = new InfrastructureStub(_testLogger, _registry); - var extension1 = infrastructure.EnsureExtension>(); - var extension2 = infrastructure.EnsureExtension>(); - extension1.Should().BeSameAs(extension2); - } -} diff --git a/NotoriousTest.UnitTests/Stubs/EnvironmentStub.cs b/NotoriousTest.UnitTests/Stubs/EnvironmentStub.cs index 0e4544f..fa22719 100644 --- a/NotoriousTest.UnitTests/Stubs/EnvironmentStub.cs +++ b/NotoriousTest.UnitTests/Stubs/EnvironmentStub.cs @@ -37,7 +37,7 @@ public EnvironmentStub(ITestLogger logger, IWatchDog watchDog, IRegistry registr public override Task ConfigureEnvironment() => OnConfigureEnvironment?.Invoke() ?? Task.CompletedTask; - public override void ConfigureInfrastructureServices(IServiceCollection collection) + protected override void ConfigureInfrastructureServices(IServiceCollection collection) { base.ConfigureInfrastructureServices(collection); diff --git a/NotoriousTest.UnitTests/Stubs/ExtensionStub.cs b/NotoriousTest.UnitTests/Stubs/ExtensionStub.cs deleted file mode 100644 index 1061d26..0000000 --- a/NotoriousTest.UnitTests/Stubs/ExtensionStub.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NotoriousTest.UnitTests.Stubs -{ - public class ExtensionStub : IInfrastructureExtension where T : NotoriousTest.Core.Infrastructures.Infrastructure - { - public Action? OnBeforeInitializeAction { get; set; } - public Action? OnAfterInitializeAction { get; set; } - public Action? OnBeforeResetAction { get; set; } - public Action? OnAfterResetAction { get; set; } - public Action? OnBeforeDestroyAction { get; set; } - public Action? OnAfterDestroyAction { get; set; } - - public Task OnBeforeInitialize(T infrastructure) { OnBeforeInitializeAction?.Invoke(infrastructure); return Task.CompletedTask; } - public Task OnAfterInitialize(T infrastructure) { OnAfterInitializeAction?.Invoke(infrastructure); return Task.CompletedTask; } - public Task OnBeforeReset(T infrastructure) { OnBeforeResetAction?.Invoke(infrastructure); return Task.CompletedTask; } - public Task OnAfterReset(T infrastructure) { OnAfterResetAction?.Invoke(infrastructure); return Task.CompletedTask; } - public Task OnBeforeDestroy(T infrastructure) { OnBeforeDestroyAction?.Invoke(infrastructure); return Task.CompletedTask; } - public Task OnAfterDestroy(T infrastructure) { OnAfterDestroyAction?.Invoke(infrastructure); return Task.CompletedTask; } - } -} diff --git a/NotoriousTest.Watchdog/DoggyDogWatchdogConfiguration.cs b/NotoriousTest.Watchdog/DoggyDogWatchdogConfiguration.cs index bfeb2ab..1d6cdaa 100644 --- a/NotoriousTest.Watchdog/DoggyDogWatchdogConfiguration.cs +++ b/NotoriousTest.Watchdog/DoggyDogWatchdogConfiguration.cs @@ -2,7 +2,7 @@ { public class DoggyDogWatchdogConfiguration { - public static string SECTION_NAME = "Watchdog"; + public const string SECTION_NAME = "Watchdog"; public bool ManualLaunch { get; set; } = false; } } diff --git a/NotoriousTest.XUnit/Environment.cs b/NotoriousTest.XUnit/Environment.cs index b720045..13fc70e 100644 --- a/NotoriousTest.XUnit/Environment.cs +++ b/NotoriousTest.XUnit/Environment.cs @@ -18,7 +18,7 @@ protected Environment(IMessageSink sink) _sink = sink; } - public override void ConfigureInfrastructureServices(IServiceCollection collection) + protected override void ConfigureInfrastructureServices(IServiceCollection collection) { base.ConfigureInfrastructureServices(collection); diff --git a/NotoriousTest.sln.DotSettings.user b/NotoriousTest.sln.DotSettings.user new file mode 100644 index 0000000..03644ab --- /dev/null +++ b/NotoriousTest.sln.DotSettings.user @@ -0,0 +1,16 @@ + + ForceIncluded + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;src&gt;\&lt;5. Samples&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="C:\Repositories\Notorious Test" Presentation="&lt;src&gt;\&lt;5. Samples&gt;" /> +</SessionState> + + + + + + + + + + + KillOrphaned \ No newline at end of file diff --git a/NotoriousTest.slnx b/NotoriousTest.slnx index b770693..6ae3611 100644 --- a/NotoriousTest.slnx +++ b/NotoriousTest.slnx @@ -57,10 +57,13 @@ - + + + + diff --git a/NotoriousTest/Environments/EnvironmentBase.cs b/NotoriousTest/Environments/EnvironmentBase.cs index 854af10..c99e367 100644 --- a/NotoriousTest/Environments/EnvironmentBase.cs +++ b/NotoriousTest/Environments/EnvironmentBase.cs @@ -16,7 +16,7 @@ namespace NotoriousTest.Environments { public abstract class EnvironmentBase : Core.Environments.EnvironmentBase { - public override void ConfigureInfrastructureServices(IServiceCollection collection) + protected override void ConfigureInfrastructureServices(IServiceCollection collection) { base.ConfigureInfrastructureServices(collection);