Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Changelog

## v5.0.0
### ✨ Features

- Dependency injection is now at the Environment level
- Create a `IDependencyInjectionConfigurator`
```csharp
public class DependencyInjectionConfigurator : IDependencyInjectionConfigurator
{
public IServiceCollection ConfigureServices(IServiceCollection services) => services;
}
```
- Add a `[InjectionConfigurator(typeof(DependencyInjectionConfigurator))]` to the test class.
- You could add multiple `InjectionConfiguratorAttribute` to the same test class (or parents), they will all be used.
- Add dependencies to the environment/infrastructure constructor !

### 💥 Breaking Changes

- Deleted support for Extensions, use dependency injection instead.
Expand Down
8 changes: 8 additions & 0 deletions NotoriousTest.Core/DI/DependencyInjectionConfigurator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.Extensions.DependencyInjection;

namespace NotoriousTest.Core.DI;

public class DependencyInjectionConfigurator : IDependencyInjectionConfigurator
{
public IServiceCollection ConfigureServices(IServiceCollection services) => services;
}
8 changes: 8 additions & 0 deletions NotoriousTest.Core/DI/IDependencyInjectionConfigurator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.Extensions.DependencyInjection;

namespace NotoriousTest.Core.DI;

public interface IDependencyInjectionConfigurator
{
IServiceCollection ConfigureServices(IServiceCollection services);
}
7 changes: 7 additions & 0 deletions NotoriousTest.Core/DI/InjectionConfiguratorAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NotoriousTest.Core.DI;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class InjectionConfiguratorAttribute(Type diConfiguratorType) : Attribute
{
public Type DIConfiguratorType { get; } = diConfiguratorType;
}
3 changes: 3 additions & 0 deletions NotoriousTest.Core/EnvironmentId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ public record EnvironmentId(Guid Value)
{
public static implicit operator Guid(EnvironmentId wrapper) => wrapper.Value;
public static implicit operator EnvironmentId(Guid guid) => new(guid);


public static EnvironmentId Create() => new(Guid.NewGuid());
}
}
77 changes: 30 additions & 47 deletions NotoriousTest.Core/Environments/EnvironmentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,31 @@ public abstract class EnvironmentBase
/// <summary>
/// Gets the unique identifier for the environment instance.
/// </summary>
public EnvironmentId EnvironmentId { get; private set; } = Guid.NewGuid();
public EnvironmentId EnvironmentId { get; } = EnvironmentId.Create();
public abstract Assembly CurrentAssembly { get; }
protected IWatchDog WatchDog { get; }
protected IRegistry Registry { get; }
protected IRuntime Runtime { get; }
protected ITestLogger Logger { get; }
protected IServiceProvider ServiceProvider { get; }

protected IServiceProvider ServiceProvider { get; private set; }
private IServiceCollection _serviceCollection;
protected IWatchDog WatchDog => field ??= ServiceProvider.GetRequiredService<IWatchDog>();
protected IRegistry Registry => field ??= ServiceProvider.GetRequiredService<IRegistry>();
protected IRuntime Runtime => field ??= ServiceProvider.GetRequiredService<IRuntime>();
protected ITestLogger Logger => field ??= ServiceProvider.GetRequiredService<ITestLogger>();

protected EnvironmentConfiguration Settings => field ??=
ServiceProvider.GetRequiredService<ITestSettingsProvider>()
?.Get<EnvironmentConfiguration>(EnvironmentConfiguration.SECTION_NAME) ?? new EnvironmentConfiguration();


/// <summary>
/// Define current test assembly.
/// </summary>
protected EnvironmentSettings Settings { get; }

public EnvironmentBase(EnvironmentSettings settings, IWatchDog watchDog, IRegistry registry, IRuntime runtime, ITestLogger logger, IServiceProvider serviceProvider)
{
Settings = settings;
WatchDog = watchDog;
Registry = registry;
Runtime = runtime;
Logger = logger;
ServiceProvider = serviceProvider;
}
/// <summary>
/// Gets the collection of infrastructure components associated with this instance.
/// </summary>
private List<Infrastructure> _infrastructures = [];


/// <summary>
/// Configuration infrastructure dependency injection.
/// </summary>
/// <returns></returns>
protected virtual void ConfigureInfrastructureServices(IServiceCollection collection) => _serviceCollection.AddSingleton(EnvironmentId);

/// <summary>
/// Configure environment with infrastructures. Called before environment initialization.
/// </summary>
Expand All @@ -79,7 +73,7 @@ public T GetInfrastructure<T>() where T : Infrastructure
/// Add an infrastructure within environment.
/// </summary>
public EnvironmentBase AddInfrastructure<T>() where T : Infrastructure
=> AddInfrastructure(ActivatorUtilities.CreateInstance<T>(ServiceProvider));
=> AddInfrastructure(ActivatorUtilities.CreateInstance<T>(ServiceProvider, EnvironmentId));

/// <summary>
/// Add an infrastructure within environment.
Expand All @@ -101,10 +95,7 @@ public EnvironmentBase AddInfrastructure(Infrastructure infrastructure)
/// <returns>A task that represents the asynchronous initialization operation.</returns>
public virtual async Task Initialize()
{
_serviceCollection = new ServiceCollection();
// Setup registry
ConfigureInfrastructureServices(_serviceCollection);
ServiceProvider = _serviceCollection.BuildServiceProvider();
if (!Settings.DisableWatchdog)
{
await SetupRegistry();
Expand All @@ -117,7 +108,7 @@ public virtual async Task Initialize()

private async Task InitializeInfrastructureInParralelAndInOrder()
{
var infrastructureGroupedByOrder = _infrastructures.OrderBy(i => i.Order).GroupBy(i => i.Order);
IEnumerable<IGrouping<int?, Infrastructure>> infrastructureGroupedByOrder = _infrastructures.OrderBy(i => i.Order).GroupBy(i => i.Order);

var action = new Func<Infrastructure, Task>(async (i) =>
{
Expand All @@ -128,10 +119,10 @@ private async Task InitializeInfrastructureInParralelAndInOrder()
await i.InitializeAsync();
});

foreach (var infrastructureGroup in infrastructureGroupedByOrder)
foreach (IGrouping<int?, Infrastructure> infrastructureGroup in infrastructureGroupedByOrder)
{
var nonConsumers = infrastructureGroup.Where(i => i is not IConfigurationConsumer);
var consumers = infrastructureGroup.Where(i => i is IConfigurationConsumer);
IEnumerable<Infrastructure> nonConsumers = infrastructureGroup.Where(i => i is not IConfigurationConsumer);
IEnumerable<Infrastructure> consumers = infrastructureGroup.Where(i => i is IConfigurationConsumer);
await Task.WhenAll(nonConsumers.Select(i => action(i)));
await Task.WhenAll(consumers.Select(i => action(i)));
}
Expand All @@ -141,17 +132,13 @@ private async Task StartDoggyDog()
{
RuntimeConfiguration? runtimeConfiguration = Runtime.GetSupportedRuntimes(CurrentAssembly);
if (runtimeConfiguration == null)
Logger.Log($"Runtime for {CurrentAssembly.FullName} cannot be found. Cleaner resolution may not work properly.");
Logger.Log($"Runtime for {CurrentAssembly.FullName} cannot be found. Cleaner resolution may not work properly.", EnvironmentId);

IEnumerable<string>? runtimesPath = runtimeConfiguration?.SupportedFrameworks?.Select(sf => sf.FilePath);
WatchDog.Start(CurrentAssembly, Process.GetCurrentProcess().Id, EnvironmentId, runtimesPath);
}

public virtual async Task Reset()
{
await ExecuteActionOnInfrastructureInParralelAndInOrder((i) => i.AutoReset ? i.ResetAsync() : Task.CompletedTask);

}
public virtual async Task Reset() => await ExecuteActionOnInfrastructureInParralelAndInOrder((i) => i.AutoReset ? i.ResetAsync() : Task.CompletedTask);

public virtual async Task Destroy()
{
Expand All @@ -164,22 +151,18 @@ public virtual async Task Destroy()

private async Task ExecuteActionOnInfrastructureInParralelAndInOrder(Func<Infrastructure, Task> action)
{
var infrastructureGroupedByOrder = _infrastructures.OrderBy(i => i.Order).GroupBy(i => i.Order);
foreach (var infrastructure in infrastructureGroupedByOrder)
IEnumerable<IGrouping<int?, Infrastructure>> infrastructureGroupedByOrder = _infrastructures.OrderBy(i => i.Order).GroupBy(i => i.Order);
foreach (IGrouping<int?, Infrastructure> infrastructure in infrastructureGroupedByOrder)
{
await Task.WhenAll(infrastructure.Select(action));
}
}



private List<ConfigurationEntry<object>> AggregateInfrastructureConfiguration()
{
return _infrastructures
.Where(i => i is IConfigurationProducer)
.SelectMany(i => (i as IConfigurationProducer).OutputConfiguration)
.ToList();
}
private List<ConfigurationEntry<object>> AggregateInfrastructureConfiguration() =>
_infrastructures
.Where(i => i is IConfigurationProducer)
.SelectMany(i => (i as IConfigurationProducer)!.OutputConfiguration)
.ToList();

private Task SetupRegistry() => Registry.Ensure();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace NotoriousTest.Core.Environments
{
public class EnvironmentConfiguration
public class EnvironmentSettings
{
public static string SECTION_NAME = "Environment";
public bool DisableWatchdog { get; set; } = false;
public bool DisableWatchdog { get; init; }
}
}
42 changes: 42 additions & 0 deletions NotoriousTest.Core/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using NotoriousTest.Core.Environments;
using NotoriousTest.Core.DI;

namespace NotoriousTest.Core;

public class Fixture<TEnvironment> where TEnvironment : EnvironmentBase
{
public TEnvironment Environment { get; private set; }
protected Type? TestClass { get; }
protected IDependencyInjectionConfigurator[] InjectionConfigurators => field ??= GetInjectionConfigurators();
private readonly IServiceProvider _services;

public Fixture(Type? testClass)
{
TestClass = testClass;
_services = ConfigureServiceCollection();
Environment = InstantiateEnvironment(_services);
}

private TEnvironment InstantiateEnvironment(IServiceProvider provider) => ActivatorUtilities.CreateInstance<TEnvironment>(provider);

private IServiceProvider ConfigureServiceCollection()
{
IServiceCollection services = new ServiceCollection();

foreach (IDependencyInjectionConfigurator dependencyInjectionConfigurator in InjectionConfigurators)
{
dependencyInjectionConfigurator.ConfigureServices(services);
}

return services.BuildServiceProvider();
}

private IDependencyInjectionConfigurator[] GetInjectionConfigurators()
{
IEnumerable<Type> diConfiguratorTypes = TestClass!.GetCustomAttributes<InjectionConfiguratorAttribute>()
.Select(ic => ic.DIConfiguratorType).ToArray();
return diConfiguratorTypes.Select((dic) => Activator.CreateInstance(dic) as IDependencyInjectionConfigurator).Where(dic => dic is not null).ToArray()!;
}
}
29 changes: 13 additions & 16 deletions NotoriousTest.Core/Infrastructures/Infrastructure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public abstract class Infrastructure : IAsyncDisposable, IInfrastructure
///<inheritdoc/>
public bool AutoReset { get; set; } = true;

public virtual bool DisableRegistry { get; } = false;
internal bool WatchdogDisabled { get; set; } = false;
public virtual bool DisableRegistry => false;
internal bool WatchdogDisabled { get; set; }
///<inheritdoc/>
public EnvironmentId EnvironmentId { get; set; }
public Guid Id = Guid.NewGuid();
Expand All @@ -59,13 +59,13 @@ public abstract class Infrastructure : IAsyncDisposable, IInfrastructure
/// <summary>
/// Gets the logger instance used to record test execution details and diagnostic information.
/// </summary>
protected ITestLogger Logger { get; private set; }
protected ITestLogger Logger { get; }

/// <summary>
/// Gets the registry provider used to track infrastructure and clean them after test crash.
/// </summary>
protected IRegistry Registry { get; private set; }
protected bool Registered { get; private set; } = false;
protected IRegistry Registry { get; }
protected bool Registered { get; private set; }

public Infrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry provider)
{
Expand All @@ -75,10 +75,7 @@ public Infrastructure(EnvironmentId contextId, ITestLogger logger, IRegistry pro
}

public abstract Task Initialize();
public virtual Task Reset()
{
return Task.CompletedTask;
}
public virtual Task Reset() => Task.CompletedTask;
public abstract Task Destroy();

public async ValueTask DisposeAsync()
Expand All @@ -90,18 +87,18 @@ internal async Task InitializeAsync()
{
try
{
Logger.Log($"[{GetType().Name}] Initialization ...");
Logger.Log($"[{GetType().Name}] Initialization ...", EnvironmentId);
var sw = Stopwatch.StartNew();

await Initialize();
if (!WatchdogDisabled && !DisableRegistry && !Registered) await Register();

Logger.Log($"[{GetType().Name}] Initialization completed in {sw.ElapsedMilliseconds} ms");
Logger.Log($"[{GetType().Name}] Initialization completed in {sw.ElapsedMilliseconds} ms", EnvironmentId);

}
catch (Exception ec)
{
Logger.Log("Initialization failed with exception: " + ec.ToString());
Logger.Log("Initialization failed with exception: " + ec, EnvironmentId);
await Destroy();
throw;
}
Expand All @@ -123,23 +120,23 @@ await Registry.Register(new InfrastuctureRegistryEntry()

internal async Task ResetAsync()
{
Logger.Log($"[{GetType().Name}] Reset ...");
Logger.Log($"[{GetType().Name}] Reset ...", EnvironmentId);
var sw = Stopwatch.StartNew();

await Reset();
if (!WatchdogDisabled && !DisableRegistry) await Registry.NotifyReset(Id);
Logger.Log($"[{GetType().Name} ] Reset completed in {sw.ElapsedMilliseconds} ms");
Logger.Log($"[{GetType().Name} ] Reset completed in {sw.ElapsedMilliseconds} ms", EnvironmentId);

}

internal async Task DestroyAsync()
{
Logger.Log($"[{GetType().Name}] Destroy ...");
Logger.Log($"[{GetType().Name}] Destroy ...", EnvironmentId);
var sw = Stopwatch.StartNew();

await Destroy();
if (!WatchdogDisabled && !DisableRegistry) await Registry.Remove(Id);

Logger.Log($"[{GetType().Name} ] Destroy completed in {sw.ElapsedMilliseconds} ms");
Logger.Log($"[{GetType().Name} ] Destroy completed in {sw.ElapsedMilliseconds} ms", EnvironmentId);
}
}
14 changes: 14 additions & 0 deletions NotoriousTest.Core/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using NotoriousTest.Core.DI;
using NotoriousTest.Core.Environments;

namespace NotoriousTest.Core;

[InjectionConfigurator(typeof(DependencyInjectionConfigurator))]
public abstract class IntegrationTestBase<T> where T : EnvironmentBase
{
protected T Environment => Fixture.Environment;
[Obsolete("Use Environment instead")]
protected T CurrentEnvironment => Fixture.Environment;
protected Fixture<T> Fixture { get; set; }

Check warning on line 12 in NotoriousTest.Core/IntegrationTestBase.cs

View workflow job for this annotation

GitHub Actions / publish

Non-nullable property 'Fixture' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 12 in NotoriousTest.Core/IntegrationTestBase.cs

View workflow job for this annotation

GitHub Actions / publish

Non-nullable property 'Fixture' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 12 in NotoriousTest.Core/IntegrationTestBase.cs

View workflow job for this annotation

GitHub Actions / publish

Non-nullable property 'Fixture' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

}
2 changes: 1 addition & 1 deletion NotoriousTest.Core/Logger/ITestLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public interface ITestLogger
{
void Log(string message);
void Log(string message, EnvironmentId environmentId);
}
}
6 changes: 5 additions & 1 deletion NotoriousTest.Core/NotoriousTest.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="NotoriousTest.UnitTests" />
<InternalsVisibleTo Include="NotoriousTest.IntegrationTests" />
<InternalsVisibleTo Include="NotoriousTest.IntegrationTests" />
<InternalsVisibleTo Include="NotoriousTest.XUnit" />
<InternalsVisibleTo Include="NotoriousTest.TUnit" />
<InternalsVisibleTo Include="NotoriousTest.NUnit" />
<InternalsVisibleTo Include="NotoriousTest.MSTest" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion NotoriousTest.Core/Runtime/IRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace NotoriousTest.Core.Runtime
{
public interface IRuntime
{
public RuntimeConfiguration GetSupportedRuntimes(Assembly assembly);
RuntimeConfiguration? GetSupportedRuntimes(Assembly assembly);
}
}
2 changes: 1 addition & 1 deletion NotoriousTest.Database/DatabaseInfrastructureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public override async Task Reset()
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);
Logger.Log(ex.Message, EnvironmentId);
}


Expand Down
Loading
Loading