feat: allow test contexts to use IServiceCollection
Some checks failed
deploy / deploy (push) Failing after 11m56s

This commit is contained in:
D. Moonfire 2024-03-08 18:19:17 -06:00
parent ed722865cc
commit a6ad0b18d1
9 changed files with 100 additions and 63 deletions

View file

@ -2,6 +2,7 @@ using FluentValidation;
using MfGames.Gallium;
using MfGames.Nitride.Entities;
using MfGames.Nitride.Generators;
using Microsoft.Extensions.Logging;
using Serilog;
using Zio;
@ -11,8 +12,11 @@ namespace MfGames.Nitride.IO.Paths;
public partial class LinkDirectChildren : CreateOrUpdateIndex
{
/// <inheritdoc />
public LinkDirectChildren(ILogger logger, IValidator<CreateOrUpdateIndex> validator)
: base(logger, validator)
public LinkDirectChildren(
ILoggerFactory loggerFactory,
IValidator<CreateOrUpdateIndex> validator
)
: base(loggerFactory, validator)
{
this.UpdateIndex = this.InternalUpdateIndex;

View file

@ -1,7 +1,7 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using MfGames.Nitride.Pipelines;
using Serilog;
using Microsoft.Extensions.Logging;
namespace MfGames.Nitride.Commands;
@ -11,14 +11,14 @@ namespace MfGames.Nitride.Commands;
/// </summary>
public class BuildCommand : Command, ICommandHandler
{
private readonly ILogger logger;
private readonly ILogger<BuildCommand> logger;
private readonly IList<IPipelineCommandOption> pipelineOptions;
private readonly PipelineManager pipelines;
public BuildCommand(
ILogger logger,
ILoggerFactory loggerFactory,
PipelineManager pipelines,
IList<IPipelineCommandOption> pipelineOptions
)
@ -27,7 +27,7 @@ public class BuildCommand : Command, ICommandHandler
// Set up our simple member variables.
this.pipelines = pipelines;
this.pipelineOptions = pipelineOptions;
this.logger = logger.ForContext<BuildCommand>();
this.logger = loggerFactory.CreateLogger<BuildCommand>();
this.Handler = this;
// Handle any injected arguments into the command line.
@ -52,11 +52,11 @@ public class BuildCommand : Command, ICommandHandler
this.pipelines.CancellationToken = cancellationToken;
// Process any injected options.
this.logger.Debug("Processing {Count:N0} pipeline options", this.pipelineOptions.Count);
this.logger.LogDebug("Processing {Count:N0} pipeline options", this.pipelineOptions.Count);
foreach (IPipelineCommandOption? option in this.pipelineOptions)
{
this.logger.Verbose("Processing pipeline option: {Option}", option);
this.logger.LogTrace("Processing pipeline option: {Option}", option);
option.Handle(context);
}
@ -64,11 +64,11 @@ public class BuildCommand : Command, ICommandHandler
// all the pipelines once and then quits when it finishes.
DateTime start = DateTime.UtcNow;
this.logger.Information("Running pipelines");
this.logger.LogInformation("Running pipelines");
int pipelinesResults = await this.pipelines.RunAsync();
this.logger.Information("Command took {Elapsed}", DateTime.UtcNow - start);
this.logger.LogInformation("Command took {Elapsed}", DateTime.UtcNow - start);
return pipelinesResults;
}

View file

@ -2,7 +2,7 @@ using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics.CodeAnalysis;
using MfGames.Nitride.Pipelines;
using Serilog;
using Microsoft.Extensions.Logging;
namespace MfGames.Nitride.Commands;
@ -12,14 +12,14 @@ namespace MfGames.Nitride.Commands;
/// </summary>
public class WatchCommand : Command, ICommandHandler
{
private readonly ILogger logger;
private readonly ILogger<WatchCommand> logger;
private readonly IList<IPipelineCommandOption> pipelineOptions;
private readonly PipelineManager pipelines;
public WatchCommand(
ILogger logger,
ILoggerFactory loggerFactory,
PipelineManager pipelines,
IList<IPipelineCommandOption> pipelineOptions
)
@ -28,7 +28,7 @@ public class WatchCommand : Command, ICommandHandler
// Set up our simple member variables.
this.pipelines = pipelines;
this.pipelineOptions = pipelineOptions;
this.logger = logger.ForContext<WatchCommand>();
this.logger = loggerFactory.CreateLogger<WatchCommand>();
this.Handler = this;
// Handle any injected arguments into the command line.
@ -53,11 +53,11 @@ public class WatchCommand : Command, ICommandHandler
this.pipelines.CancellationToken = cancellationToken;
// Process any injected options.
this.logger.Debug("Processing {Count:N0} pipeline options", this.pipelineOptions.Count);
this.logger.LogDebug("Processing {Count:N0} pipeline options", this.pipelineOptions.Count);
foreach (IPipelineCommandOption option in this.pipelineOptions)
{
this.logger.Verbose("Processing pipeline option: {Option}", option);
this.logger.LogTrace("Processing pipeline option: {Option}", option);
option.Handle(context);
}
@ -78,8 +78,8 @@ public class WatchCommand : Command, ICommandHandler
// The watch part is a periodic poll that looks for at least one
// pipeline that is marked "stale". After that first one, it starts a
// countdown to catch any other changes before triggering a build.
var restartDelay = TimeSpan.FromSeconds(1);
var pollDelay = TimeSpan.FromMilliseconds(100);
TimeSpan restartDelay = TimeSpan.FromSeconds(1);
TimeSpan pollDelay = TimeSpan.FromMilliseconds(100);
while (!cancellationToken.IsCancellationRequested)
{
@ -108,12 +108,12 @@ public class WatchCommand : Command, ICommandHandler
)]
private async Task<bool> RunPipelinesAsync(string startMessage, string finishMessage)
{
this.logger.Information(startMessage);
this.logger.LogInformation(startMessage);
DateTime start = DateTime.UtcNow;
int pipelinesResults = await this.pipelines.RunAsync();
this.logger.Information(finishMessage, DateTime.UtcNow - start);
this.logger.LogInformation(finishMessage, DateTime.UtcNow - start);
return pipelinesResults != 0;
}

View file

@ -1,7 +1,7 @@
using FluentValidation;
using MfGames.Gallium;
using MfGames.Nitride.Generators;
using Serilog;
using Microsoft.Extensions.Logging;
namespace MfGames.Nitride.Entities;
@ -19,14 +19,17 @@ namespace MfGames.Nitride.Entities;
[WithProperties]
public partial class CreateOrUpdateIndex : OperationBase
{
private readonly ILogger logger;
private readonly ILogger<CreateOrUpdateIndex> logger;
private readonly IValidator<CreateOrUpdateIndex> validator;
public CreateOrUpdateIndex(ILogger logger, IValidator<CreateOrUpdateIndex> validator)
public CreateOrUpdateIndex(
ILoggerFactory loggerFactory,
IValidator<CreateOrUpdateIndex> validator
)
{
this.validator = validator;
this.logger = logger.ForContext(typeof(CreateOrUpdateIndex));
this.logger = loggerFactory.CreateLogger<CreateOrUpdateIndex>();
}
/// <summary>
@ -63,7 +66,8 @@ public partial class CreateOrUpdateIndex : OperationBase
this.validator.ValidateAndThrow(this);
// Get the list of all the scanned entities.
var scanned = this.Scanner.GetScannedResults().ToDictionary(x => x.Key, x => x.Value);
Dictionary<string, List<Entity>> scanned = this.Scanner.GetScannedResults()
.ToDictionary(x => x.Key, x => x.Value);
// We loop through the results and look for index entities. Any one we
// find, we update with the existing entries. If we get to the end and
@ -103,7 +107,7 @@ public partial class CreateOrUpdateIndex : OperationBase
}
// Report the results.
this.logger.Debug(
this.logger.LogDebug(
"Found {Old:N0} and created {New:N0} index pages for {Keys:N0} keys",
existing.Count,
scanned.Count - existing.Count,

View file

@ -1,24 +1,24 @@
using Humanizer;
using Serilog;
using Serilog.Core;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace MfGames.Nitride.Pipelines.Observers;
/// <summary>
/// An observer that logs at the verbose level the transitions between the
/// An observer that logs at the.LogTrace level the transitions between the
/// pipelines including how long they take. This is useful for debugging the
/// pipelines themselves.
/// </summary>
public class LoggingPipelineObserver : IPipelineObserver
{
private readonly ILogger baseLogger;
private readonly ILogger<LoggingPipelineObserver> baseLogger;
private readonly Logger silentLogger;
private readonly ILoggerFactory loggerFactory;
public LoggingPipelineObserver(ILogger logger)
public LoggingPipelineObserver(ILoggerFactory loggerFactory)
{
this.baseLogger = logger;
this.silentLogger = new LoggerConfiguration().CreateLogger();
this.loggerFactory = loggerFactory;
this.baseLogger = loggerFactory.CreateLogger<LoggingPipelineObserver>();
}
/// <summary>
@ -36,7 +36,7 @@ public class LoggingPipelineObserver : IPipelineObserver
)
{
this.GetLogger(runner)
.Verbose(
.LogTrace(
"Transition {Trigger} changed state {Old} to {New} (elapsed {Elapsed}, duration {Duration})",
trigger,
oldState,
@ -53,7 +53,7 @@ public class LoggingPipelineObserver : IPipelineObserver
)
{
this.GetLogger(runner)
.Verbose(
.LogTrace(
"{Runner} signalled, waiting for {Count:n0} more",
outgoing,
currentBeforeDone
@ -64,20 +64,20 @@ public class LoggingPipelineObserver : IPipelineObserver
public void OnGetInputsFinished(PipelineRunner runner, int inputCount)
{
this.GetLogger(runner)
.Debug("Got {Count:l} from dependencies", "entity".ToQuantity(inputCount, "N0"));
.LogDebug("Got {Count:l} from dependencies", "entity".ToQuantity(inputCount, "N0"));
}
/// <inheritdoc />
public void OnGetInputsStarted(PipelineRunner runner)
{
this.GetLogger(runner)
.Verbose("Gathering outputs from {Count:n0} dependencies", runner.Incoming.Count);
.LogTrace("Gathering outputs from {Count:n0} dependencies", runner.Incoming.Count);
}
/// <inheritdoc />
public void OnRunFinished(PipelineRunner runner)
{
this.GetLogger(runner).Verbose("Gathering output from incoming pipelines");
this.GetLogger(runner).LogTrace("Gathering output from incoming pipelines");
}
/// <inheritdoc />
@ -90,33 +90,33 @@ public class LoggingPipelineObserver : IPipelineObserver
public void OnSendDoneToIncoming(PipelineRunner runner)
{
this.GetLogger(runner)
.Verbose("Signaling {Count:n0} dependencies done", runner.Incoming.Count);
.LogTrace("Signaling {Count:n0} dependencies done", runner.Incoming.Count);
}
/// <inheritdoc />
public void OnUnlockReleased(PipelineRunner runner)
{
this.GetLogger(runner).Verbose("Release manual reset for consumers");
this.GetLogger(runner).LogTrace("Release manual reset for consumers");
}
/// <inheritdoc />
public void OnUnlockStarted(PipelineRunner runner)
{
this.GetLogger(runner).Verbose("Setting up internal thread controls");
this.GetLogger(runner).LogTrace("Setting up internal thread controls");
}
/// <inheritdoc />
public void OnUnlockStatus(PipelineRunner runner, int count)
{
this.GetLogger(runner)
.Debug("Output {Count:l} from pipeline", "entity".ToQuantity(count, "N0"));
.LogDebug("Output {Count:l} from pipeline", "entity".ToQuantity(count, "N0"));
}
/// <inheritdoc />
public void OnWaitForIncomingStarted(PipelineRunner runner)
{
this.GetLogger(runner)
.Verbose(
.LogTrace(
"Waiting for {Count:l} to complete",
"dependency".ToQuantity(runner.Incoming.Count)
);
@ -125,7 +125,7 @@ public class LoggingPipelineObserver : IPipelineObserver
private ILogger GetLogger(PipelineRunner runner)
{
return this.Enable
? this.baseLogger.ForContext(runner.Pipeline.GetType())
: this.silentLogger;
? this.loggerFactory.CreateLogger(runner.Pipeline.GetType())
: NullLogger.Instance;
}
}

View file

@ -1,5 +1,5 @@
using Humanizer;
using Serilog;
using Microsoft.Extensions.Logging;
namespace MfGames.Nitride.Pipelines;
@ -10,19 +10,19 @@ namespace MfGames.Nitride.Pipelines;
/// </summary>
public class PipelineManager
{
private readonly ILogger logger;
private readonly ILogger<PipelineManager> logger;
private readonly List<PipelineRunner> runners;
private bool isSetup;
public PipelineManager(
ILogger logger,
ILoggerFactory loggerFactory,
IEnumerable<IPipeline> pipelines,
PipelineRunner.Factory runnerFactory
)
{
this.logger = logger.ForContext<PipelineManager>();
this.logger = loggerFactory.CreateLogger<PipelineManager>();
this.runners = pipelines.Select(pipeline => runnerFactory(pipeline)).ToList();
}
@ -58,7 +58,7 @@ public class PipelineManager
// Go through all the entries and start each one. We gather the
// resulting tasks and then wait for all of them to end.
this.logger.Verbose("Starting {Count:l}", "pipeline".ToQuantity(this.runners.Count));
this.logger.LogTrace("Starting {Count:l}", "pipeline".ToQuantity(this.runners.Count));
Task[] tasks = this.runners.Select(x =>
Task.Run(async () => await x.StartAsync(this.CancellationToken), this.CancellationToken)
@ -71,7 +71,7 @@ public class PipelineManager
{
List<PipelineRunner> waiting = this.runners.Where(x => !x.IsFinished).ToList();
this.logger.Debug(
this.logger.LogDebug(
"Waiting for {Count:l} to finish running",
"pipeline".ToQuantity(waiting.Count)
);
@ -86,7 +86,7 @@ public class PipelineManager
.OrderBy(x => x.Pipeline.ToString())
.ToList();
this.logger.Verbose(
this.logger.LogTrace(
"Waiting for {Count:l} in {State}: {List:l}",
"pipeline".ToQuantity(statePipelines.Count),
state.Key,
@ -103,7 +103,7 @@ public class PipelineManager
// Figure out our return code.
bool hasErrors = this.runners.Any(x => x.State == PipelineRunnerState.Errored);
this.logger.Information("Completed in {Elapsed}", DateTime.UtcNow - started);
this.logger.LogInformation("Completed in {Elapsed}", DateTime.UtcNow - started);
return Task.FromResult(hasErrors ? 2 : 0);
}
@ -139,7 +139,7 @@ public class PipelineManager
// If we don't have any pipelines, then we can't process.
if (this.runners.Count == 0)
{
this.logger.Error(
this.logger.LogError(
"There are no registered pipelines run, use"
+ " ConfigureContainer to include IPipeline instances"
);
@ -147,7 +147,7 @@ public class PipelineManager
return false;
}
this.logger.Verbose("Setting up {Count:l}", "pipeline".ToQuantity(this.runners.Count));
this.logger.LogTrace("Setting up {Count:l}", "pipeline".ToQuantity(this.runners.Count));
// Go through and connect the pipelines together using the dependencies
// that were built through the constructors of the pipelines and then

View file

@ -1,6 +1,6 @@
using MfGames.Gallium;
using MfGames.Nitride.Pipelines.Observers;
using Serilog;
using Microsoft.Extensions.Logging;
using RunnerStateMachine = Stateless.StateMachine<
MfGames.Nitride.Pipelines.PipelineRunnerState,
MfGames.Nitride.Pipelines.PipelineRunnerTrigger
@ -48,14 +48,18 @@ public class PipelineRunner
/// </summary>
private int waitingOnConsumers;
public PipelineRunner(ILogger logger, PipelineObserverManager observers, IPipeline pipeline)
public PipelineRunner(
ILoggerFactory loggerFactory,
PipelineObserverManager observers,
IPipeline pipeline
)
{
this.observers = observers;
this.Pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline));
this.Incoming = new List<PipelineRunner>();
this.Outgoing = new List<PipelineRunner>();
this.outputs = new List<Entity>();
this.logger = logger.ForContext(this.Pipeline.GetType());
this.logger = loggerFactory.CreateLogger(this.Pipeline.GetType());
this.outgoingBlock = new ManualResetEventSlim(false);
this.outgoingDone = new ManualResetEventSlim(false);
this.started = DateTime.Now;
@ -318,7 +322,7 @@ public class PipelineRunner
}
catch (Exception exception)
{
this.logger.Error(exception, "There was an exception running pipeline");
this.logger.LogError(exception, "There was an exception running pipeline");
await this.state.FireAsync(PipelineRunnerTrigger.PipelineFailed);
}
@ -361,7 +365,7 @@ public class PipelineRunner
return;
}
this.logger.Error("There was an exception in a dependency");
this.logger.LogError("There was an exception in a dependency");
this.state.Fire(PipelineRunnerTrigger.PipelineFailed);
}
@ -412,7 +416,7 @@ public class PipelineRunner
{
if (!this.IsStale)
{
this.logger.Verbose("The pipeline was marked stale because of {Type}", type);
this.logger.LogTrace("The pipeline was marked stale because of {Type}", type);
this.IsStale = true;
}

View file

@ -40,4 +40,10 @@
<PackageReference Include="xunit" Version="2.5.0"/>
</ItemGroup>
<ItemGroup>
<Reference Include="Autofac.Extensions.DependencyInjection">
<HintPath>..\..\..\..\..\.nuget\packages\autofac.extensions.dependencyinjection\8.0.0\lib\net6.0\Autofac.Extensions.DependencyInjection.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -1,4 +1,6 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
namespace MfGames.TestSetup;
@ -26,15 +28,30 @@ public class TestContext : IDisposable
private set => this.logger = value;
}
/// <summary>
/// Gets the service provider for resolving services.
/// </summary>
public AutofacServiceProvider ServiceProvider { get; private set; }
public void ConfigureContainer()
{
var builder = new ContainerBuilder();
// Set up the Microsoft services so we can use things like
// ILoggerFactory and other generic services.
ServiceCollection services = new();
ContainerBuilder builder = new();
services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(this.logger));
this.ConfigureServices(services);
builder.Populate(services);
// Set up the Autofac container next.
builder.RegisterInstance(this.Logger).As<ILogger>().SingleInstance();
this.ConfigureContainer(builder);
this.Container = builder.Build();
this.ServiceProvider = new AutofacServiceProvider(this.Container);
}
/// <inheritdoc />
@ -55,4 +72,6 @@ public class TestContext : IDisposable
}
protected virtual void ConfigureContainer(ContainerBuilder builder) { }
protected virtual void ConfigureServices(ServiceCollection builder) { }
}