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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v5.0.1

### 🐛 Bug Fixes

- Fixed a bug where internal packages were not available at installation. Preventing NotoriousTest from being installed.
- All internal packages are now available to download from Nuget.org, but every class is internal. Which makes them
impossible to use.

## v5.0.0

### ✨ Features
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<VersionPrefix>5.0.0</VersionPrefix>
<VersionPrefix>5.0.1</VersionPrefix>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>high</NuGetAuditLevel>
<NuGetAuditMode>all</NuGetAuditMode>
Expand Down
4 changes: 2 additions & 2 deletions DoggyDog/DoggyDog.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

<ItemGroup>
<ProjectReference Include="..\NotoriousTest.Core\NotoriousTest.Core.csproj" />
<ProjectReference Include="..\NotoriousTest.SqlLiteRegistry\NotoriousTest.SqlLiteRegistry.csproj" />
<ProjectReference Include="..\NotoriousTest.Watchdog\NotoriousTest.Watchdog.csproj" />
<ProjectReference Include="..\NotoriousTest.Internal.SqlLiteRegistry\NotoriousTest.Internal.SqlLiteRegistry.csproj" />
<ProjectReference Include="..\NotoriousTest.Internal.Watchdog\NotoriousTest.Internal.Watchdog.csproj" />
</ItemGroup>

<Target Name="PublishDoggyDogToArtifacts" AfterTargets="Build" Condition="'$(PublishDoggyDogToArtifactsOnBuild)' != 'false'">
Expand Down
2 changes: 1 addition & 1 deletion DoggyDog/DoggyDogRecoveryWatchdog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using NotoriousTest.Core;
using NotoriousTest.Core.Infrastructures.Cleaner;
using NotoriousTest.Core.Registry;
using NotoriousTest.Watchdog;
using NotoriousTest.Internal.Watchdog;

using System.Diagnostics;
using System.Reflection;
Expand Down
2 changes: 1 addition & 1 deletion DoggyDog/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

using Microsoft.Data.Sqlite;

using NotoriousTest.SqlLiteRegistry;
using NotoriousTest.Internal.SqlLiteRegistry;


Logger logger = Logger.Instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using NotoriousTest.Core.Logger;
using NotoriousTest.Core.Registry;
using NotoriousTest.Sqlite;
using NotoriousTest.SqlLiteRegistry;
using NotoriousTest.Internal.SqlLiteRegistry;

using System.Diagnostics;
using System.Runtime.InteropServices;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using NotoriousTest.Core;
using NotoriousTest.Core.Logger;
using NotoriousTest.IntegrationTests.SystemUnderTest;
using NotoriousTest.SqlLiteRegistry;
using NotoriousTest.Internal.SqlLiteRegistry;
using NotoriousTest.XUnit;

namespace NotoriousTest.IntegrationTests.Infrastructure;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>NotoriousTest.Internal.Runtime</PackageId>
<Description>[Internal] Provides the runtime configuration layer for NotoriousTest. Not intended for direct consumption — use the NotoriousTest package instead.</Description>
<IsPackable>true</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NotoriousTest.Core\NotoriousTest.Core.csproj"/>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="NotoriousTest"/>
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions NotoriousTest.Internal.Runtime/RuntimeConfigurationEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NotoriousTest.Internal.Runtime;

internal record RuntimeConfigurationEntity(RuntimeOptions RuntimeOptions);

internal record RuntimeOptions(
string Tfm,
FrameworkEntry? Framework,
FrameworkEntry[]? Frameworks
);

internal record FrameworkEntry(string Name, string Version);
88 changes: 88 additions & 0 deletions NotoriousTest.Internal.Runtime/RuntimeConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using NotoriousTest.Core.Logger;
using NotoriousTest.Core.Runtime;

namespace NotoriousTest.Internal.Runtime;

internal class RuntimeConfigurationProvider : IRuntime
{
private const string RuntimeFile = "runtimeconfig.json";
private readonly ITestLogger _logger;

private readonly JsonSerializerOptions JSON_OPTIONS = new() { PropertyNameCaseInsensitive = true };
private RuntimeConfiguration? _cache;

public RuntimeConfigurationProvider(ITestLogger logger)
{
_logger = logger;
}

public RuntimeConfiguration? GetSupportedRuntimes(Assembly executingAssembly)
{
if (_cache != null) return _cache;

string assemblyPath = executingAssembly.Location;
string runtimePath = Path.ChangeExtension(assemblyPath, RuntimeFile);

if (!File.Exists(runtimePath))
return null;


string json = File.ReadAllText(runtimePath);


RuntimeConfigurationEntity? entity = JsonSerializer.Deserialize<RuntimeConfigurationEntity>(json, JSON_OPTIONS);
if (entity == null)
return null;

FrameworkEntry[]? frameworkEntries = entity.RuntimeOptions.Frameworks ?? [entity.RuntimeOptions.Framework!];
SupportedFramework[]? frameworks = frameworkEntries.Select(ToDomain).ToArray();

var result = new RuntimeConfiguration(entity.RuntimeOptions.Tfm, frameworks);
_cache = result;
return result;
}

private SupportedFramework ToDomain(FrameworkEntry entry) =>
new(entry.Name, entry.Version, GetNearestVersionDirectory(entry));

private string? GetNearestVersionDirectory(FrameworkEntry framework)
{
string dotnetRoot = GetDotnetRoot();

string frameworkPath = $"{dotnetRoot}/shared/{framework.Name}";

string prefix = $"{new Version(framework.Version).Major}.{new Version(framework.Version).Minor}.";
// Find the nearest version
string? best = Directory.GetDirectories(frameworkPath)
.Where(d => Path.GetFileName(d).StartsWith(prefix))
.OrderByDescending(d => new Version(Path.GetFileName(d)))
.FirstOrDefault();

return best;
}

private string GetDotnetRoot()
{
string? dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT");

if (!string.IsNullOrWhiteSpace(dotnetRoot))
return dotnetRoot;

string mainModule = Process.GetCurrentProcess().MainModule!.FileName;

// If the test project is self-contained (wich is rare), the main module will be the test executable itself.
bool isNotSelfContained = Path.GetFileNameWithoutExtension(mainModule)
.Equals("dotnet", StringComparison.OrdinalIgnoreCase);

if (isNotSelfContained)
return Path.GetDirectoryName(mainModule)!;

return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
"dotnet"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Globalization;
using System.Text.Json;
using NotoriousTest.Core.Registry;

namespace NotoriousTest.Internal.SqlLiteRegistry;

internal class InfrastructureRegistryEntryEntity
{
public string InfrastructureId { get; set; }
public string InfrastructureType { get; set; }
public string EnvironmentId { get; set; }
public int ProcessID { get; set; }
public string? Metadata { get; set; }
public string? MetadataType { get; set; }
public string? LastResetDate { get; set; }
public string CreationDate { get; set; }
public string UpdateDate { get; set; }

internal static InfrastructureRegistryEntryEntity? FromDomain(InfrastuctureRegistryEntry entry) =>
new()
{
InfrastructureId = entry.InfrastructureId.ToString(),
InfrastructureType = entry.InfrastructureType.AssemblyQualifiedName,
EnvironmentId = entry.EnvironmentId.ToString(),
ProcessID = entry.ProcessID,
Metadata = JsonSerializer.Serialize(entry.Metadata),
MetadataType = entry.Metadata?.GetType().AssemblyQualifiedName,
LastResetDate = entry.LastResetDate?.ToString("O")
};

internal InfrastuctureRegistryEntry ToDomain()
{
Type? metadataType = null;

if (MetadataType != null) metadataType = Type.GetType(MetadataType);

var infraType = Type.GetType(InfrastructureType);
if (infraType == null) throw new TypeLoadException($"Unable to load type {InfrastructureType}");

return new InfrastuctureRegistryEntry
{
InfrastructureId = Guid.Parse(InfrastructureId),
InfrastructureType = Type.GetType(InfrastructureType),
EnvironmentId = Guid.Parse(EnvironmentId),
ProcessID = ProcessID,
Metadata = !(Metadata is null) && !(metadataType is null)
? JsonSerializer.Deserialize(Metadata, metadataType)
: null,
LastResetDate =
LastResetDate != null ? DateTime.Parse(LastResetDate, null, DateTimeStyles.RoundtripKind) : null,
CreationDate = DateTime.Parse(CreationDate, null, DateTimeStyles.RoundtripKind),
UpdateDate = DateTime.Parse(UpdateDate, null, DateTimeStyles.RoundtripKind)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>NotoriousTest.Internal.SqlLiteRegistry</PackageId>
<Description>[Internal] SQLite-backed infrastructure registry for NotoriousTest. Not intended for direct consumption — use the NotoriousTest package instead.</Description>
<IsPackable>true</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NotoriousTest.Core\NotoriousTest.Core.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper"/>
<PackageReference Include="Microsoft.Data.Sqlite"/>
<PackageReference Include="System.Text.Json"/>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="NotoriousTest.UnitTests"/>
<InternalsVisibleTo Include="NotoriousTest.IntegrationTests"/>
<InternalsVisibleTo Include="NotoriousTest"/>
<InternalsVisibleTo Include="NotoriousTest.Core"/>
<InternalsVisibleTo Include="NotoriousTest.Internal.Watchdog"/>
<InternalsVisibleTo Include="DoggyDog"/>
</ItemGroup>
</Project>
126 changes: 126 additions & 0 deletions NotoriousTest.Internal.SqlLiteRegistry/SqliteRegistryProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.Data;
using Dapper;
using Microsoft.Data.Sqlite;
using NotoriousTest.Core;
using NotoriousTest.Core.Registry;

namespace NotoriousTest.Internal.SqlLiteRegistry;

internal partial class SqliteRegistryProvider : IRegistry
{
private readonly SqliteRegistryProviderConfiguration _configuration;

public SqliteRegistryProvider(SqliteRegistryProviderConfiguration configuration)
{
_configuration = configuration;
}

private SqliteConnectionStringBuilder ConnectionString => new(_configuration.ConnectionString);

private SqliteConnection Connection
{
get
{
var connection = new SqliteConnection(ConnectionString.ToString());

if (connection.State != ConnectionState.Open) connection.Open();

connection.Execute("PRAGMA SYNCHRONOUS=NORMAL;PRAGMA JOURNAL_MODE=WAL;");
return connection;
}
}

public event Action<InfrastuctureRegistryEntry> OnInfrastructureReset;
public event Action<InfrastuctureRegistryEntry> OnInfrastructureDestroyed;
public event Action<InfrastuctureRegistryEntry> OnInfrastructureCreated;


public async Task Ensure()
{
string directory = Path.GetDirectoryName(ConnectionString.DataSource);
if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);

await using SqliteConnection connection = Connection;

await connection.ExecuteAsync(ENSURE_REGISTRY);
}

public async Task<InfrastuctureRegistryEntry> Register(InfrastuctureRegistryEntry entry)
{
await using SqliteConnection connection = Connection;
InfrastructureRegistryEntryEntity entity =
await connection.QuerySingleAsync<InfrastructureRegistryEntryEntity>(REGISTER_INFRASTRUCTURE,
InfrastructureRegistryEntryEntity.FromDomain(entry));

return entity.ToDomain();
}

public async Task<bool> Remove(Guid id)
{
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 using SqliteConnection connection = Connection;

await connection.ExecuteAsync(UPDATE_INFRASTRUCTURE_RESET_DATE, new { InfrastructureId = id.ToString() });
}

public async Task<IEnumerable<InfrastuctureRegistryEntry>> GetByProcessId(int processId)
{
await using SqliteConnection connection = Connection;

IEnumerable<InfrastructureRegistryEntryEntity> entries =
await connection.QueryAsync<InfrastructureRegistryEntryEntity>(GET_BY_PROCESS_ID,
new { ProcessID = processId });
return entries.Select(entry => entry.ToDomain());
}

public async Task<IEnumerable<InfrastuctureRegistryEntry>> GetByEnvironmentId(EnvironmentId environmentId)
{
await using SqliteConnection connection = Connection;

IEnumerable<InfrastructureRegistryEntryEntity> entries =
await connection.QueryAsync<InfrastructureRegistryEntryEntity>(GET_BY_ENVIRONMENT_ID,
new { EnvironmentId = environmentId.Value.ToString() });
return entries.Select(entry => entry.ToDomain());
}

public async Task Watch(EnvironmentId environmentId, CancellationToken ct)
{
var snapshot = (await GetByEnvironmentId(environmentId)).ToDictionary(x => x.InfrastructureId);

do
{
IEnumerable<InfrastuctureRegistryEntry> infras = await GetByEnvironmentId(environmentId);

foreach (InfrastuctureRegistryEntry infra in infras)
{
if (!snapshot.TryGetValue(infra.InfrastructureId, out InfrastuctureRegistryEntry? previous))
OnInfrastructureCreated?.Invoke(infra);

if (previous is not null && infra.LastResetDate != previous.LastResetDate)
OnInfrastructureReset?.Invoke(infra);
}

foreach ((Guid key, InfrastuctureRegistryEntry infrastructure) in snapshot)
if (!infras.Any(i => i.InfrastructureId == infrastructure.InfrastructureId))
OnInfrastructureDestroyed?.Invoke(infrastructure);

snapshot = infras.ToDictionary(x => x.InfrastructureId);

try
{
await Task.Delay(10, ct);
}
catch (OperationCanceledException)
{
}
} while (!ct.IsCancellationRequested);
}
}
Loading
Loading