diff --git a/Directory.Build.props b/Directory.Build.props index 5110cdb..723456e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,20 +33,17 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + all 3.9.50 - + \ No newline at end of file diff --git a/aet.png b/aet.png new file mode 100644 index 0000000..ac47943 Binary files /dev/null and b/aet.png differ diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index 5103bad..e12f6ce 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit 5103bad6f09ba88061ccbc36ee285ee9300744cc +Subproject commit e12f6ceedb83fe9e3372dd89c68d508f8479cf92 diff --git a/src/ModVerify.CliApp/App/CreateBaselineAction.cs b/src/ModVerify.CliApp/App/CreateBaselineAction.cs index b0eb880..b776e09 100644 --- a/src/ModVerify.CliApp/App/CreateBaselineAction.cs +++ b/src/ModVerify.CliApp/App/CreateBaselineAction.cs @@ -2,10 +2,10 @@ using AET.ModVerify.App.Settings; using AET.ModVerify.App.Utilities; using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; using System.IO.Abstractions; using System.Threading.Tasks; @@ -26,16 +26,14 @@ protected override void PrintAction(VerificationTarget target) Console.WriteLine(); } - protected override async Task ProcessVerifyFindings( - VerificationTarget verificationTarget, - IReadOnlyCollection allErrors) + protected override async Task ProcessResult(VerificationResult result) { var baselineFactory = ServiceProvider.GetRequiredService(); - var baseline = baselineFactory.CreateBaseline(verificationTarget, Settings, allErrors); + var baseline = baselineFactory.CreateBaseline(result.Target, Settings, result.Errors); var fullPath = _fileSystem.Path.GetFullPath(Settings.NewBaselinePath); Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Writing Baseline to '{FullPath}' with {Number} findings", fullPath, allErrors.Count); + "Writing Baseline to '{FullPath}' with {Number} findings", fullPath, result.Errors.Count); await baselineFactory.WriteBaselineAsync(baseline, Settings.NewBaselinePath); @@ -43,7 +41,7 @@ protected override async Task ProcessVerifyFindings( Console.WriteLine(); Console.ForegroundColor = ConsoleColor.DarkGreen; - Console.WriteLine($"Baseline for {verificationTarget.Name} created."); + Console.WriteLine($"Baseline for {result.Target.Name} created."); Console.ResetColor(); return ModVerifyConstants.Success; diff --git a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs index b3f12f7..113e633 100644 --- a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs +++ b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs @@ -1,13 +1,13 @@ using System; -using System.Collections.Generic; using System.IO.Abstractions; using System.Threading.Tasks; using AET.ModVerify.App.GameFinder; using AET.ModVerify.App.Reporting; using AET.ModVerify.App.Settings; using AET.ModVerify.App.TargetSelectors; -using AET.ModVerify.Pipeline; using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; +using AET.ModVerify.Reporting.Suppressions; using AnakinRaW.ApplicationBase; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -71,43 +71,55 @@ public async Task ExecuteAsync() PrintAction(verificationTarget); - var allErrors = await VerifyTargetAsync(verificationTarget) + var verificationResult = await VerifyTargetAsync(verificationTarget) .ConfigureAwait(false); - return await ProcessVerifyFindings(verificationTarget, allErrors); + return await ProcessResult(verificationResult); } - protected abstract Task ProcessVerifyFindings( - VerificationTarget verificationTarget, - IReadOnlyCollection allErrors); + protected abstract Task ProcessResult(VerificationResult result); protected abstract VerificationBaseline GetBaseline(VerificationTarget verificationTarget); - private async Task> VerifyTargetAsync(VerificationTarget verificationTarget) + private async Task VerifyTargetAsync(VerificationTarget verificationTarget) { var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name, Settings.ReportSettings); var baseline = GetBaseline(verificationTarget); var suppressions = GetSuppressions(); - using var verifyPipeline = new GameVerifyPipeline( - verificationTarget, - Settings.VerifyPipelineSettings, - progressReporter, - new EngineInitializeProgressReporter(verificationTarget.Engine), - baseline, - suppressions, - ServiceProvider); - try { + var verifierService = ServiceProvider.GetRequiredService(); + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verifying '{Target}'...", verificationTarget.Name); - await verifyPipeline.RunAsync().ConfigureAwait(false); + + var verificationResult = await verifierService.VerifyAsync( + verificationTarget, + Settings.VerifierServiceSettings, + baseline, + suppressions, + progressReporter, + new EngineInitializeProgressReporter(verificationTarget.Engine)); + progressReporter.Report(string.Empty, 1.0); - } - catch (OperationCanceledException) - { - Logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Verification stopped due to enabled failFast setting."); + + switch (verificationResult.Status) + { + case VerificationCompletionStatus.CompletedFailFast: + Logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Verification stopped due to enabled failFast setting."); + break; + case VerificationCompletionStatus.Cancelled: + Logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Verification was cancelled."); + break; + case VerificationCompletionStatus.Completed: + default: + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verification completed successfully."); + break; + } + + return verificationResult; + } catch (Exception e) { @@ -119,9 +131,6 @@ private async Task> VerifyTargetAsync(Ver { progressReporter.Dispose(); } - - Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Finished verification"); - return verifyPipeline.FilteredErrors; } private SuppressionList GetSuppressions() diff --git a/src/ModVerify.CliApp/App/VerifyAction.cs b/src/ModVerify.CliApp/App/VerifyAction.cs index 0cfea0e..f30305c 100644 --- a/src/ModVerify.CliApp/App/VerifyAction.cs +++ b/src/ModVerify.CliApp/App/VerifyAction.cs @@ -1,12 +1,14 @@ using AET.ModVerify.App.Reporting; using AET.ModVerify.App.Settings; +using AET.ModVerify.App.Utilities; using AET.ModVerify.Reporting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AET.ModVerify.App.Utilities; +using AET.ModVerify.Reporting.Reporters; +using AET.ModVerify.Reporting.Baseline; namespace AET.ModVerify.App; @@ -23,20 +25,27 @@ protected override void PrintAction(VerificationTarget target) Console.WriteLine(); } - protected override async Task ProcessVerifyFindings( - VerificationTarget verificationTarget, - IReadOnlyCollection allErrors) + protected override async Task ProcessResult(VerificationResult result) { Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Reporting Errors..."); - var reportBroker = new VerificationReportBroker(ServiceProvider); - await reportBroker.ReportAsync(allErrors); + var reportBroker = new VerificationReportBroker(CreateReporters(), ServiceProvider); + + result = result with + { + Target = result.Target with + { + Location = result.Target.Location.MaskUsername() + } + }; + + await reportBroker.ReportAsync(result); if (Settings.AppFailsOnMinimumSeverity.HasValue && - allErrors.Any(x => x.Severity >= Settings.AppFailsOnMinimumSeverity)) + result.Errors.Any(x => x.Severity >= Settings.AppFailsOnMinimumSeverity)) { Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "The verification of {Target} completed with findings of the specified failure severity {Severity}", - verificationTarget.Name, Settings.AppFailsOnMinimumSeverity); + result.Target.Name, Settings.AppFailsOnMinimumSeverity); return ModVerifyConstants.CompletedWithFindings; } @@ -57,4 +66,32 @@ protected override VerificationBaseline GetBaseline(VerificationTarget verificat } return baseline; } + + private IReadOnlyCollection CreateReporters() + { + var reporters = new List(); + + reporters.Add(IVerificationReporter.CreateConsole(new ConsoleReporterSettings + { + MinimumReportSeverity = Settings.VerifierServiceSettings.FailFastSettings.IsFailFast + ? VerificationSeverity.Information + : VerificationSeverity.Error + }, ServiceProvider)); + + var outputDirectory = Settings.ReportDirectory; + reporters.Add(IVerificationReporter.CreateJson(new JsonReporterSettings + { + OutputDirectory = outputDirectory, + MinimumReportSeverity = Settings.ReportSettings.MinimumReportSeverity, + AggregateResults = true + }, ServiceProvider)); + + reporters.Add(IVerificationReporter.CreateText(new TextFileReporterSettings + { + OutputDirectory = outputDirectory!, + MinimumReportSeverity = Settings.ReportSettings.MinimumReportSeverity + }, ServiceProvider)); + + return reporters; + } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj index ec2fd46..7dcf361 100644 --- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj +++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj @@ -5,14 +5,14 @@ Exe AET.ModVerify.App ModVerify - $(RepoRootPath)aet.ico + Resources/aet.ico AlamoEngineTools.ModVerify.CliApp ModVerify Console Application AET.ModVerify - Console application that analyzes game modifications for Empire at War / Forces of Corruption for common errors. + An application that analyzes game modifications for Empire at War / Forces of Corruption for common errors. alamo,petroglyph,glyphx @@ -25,10 +25,6 @@ true - - - - @@ -70,7 +66,7 @@ compile runtime; build; native; contentfiles; analyzers; buildtransitive - + compile runtime; build; native; contentfiles; analyzers; buildtransitive @@ -86,10 +82,6 @@ - - - - diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index 2d23b3b..68056a4 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -2,11 +2,6 @@ using AET.ModVerify.App.Settings.CommandLine; using AET.ModVerify.App.Updates; using AET.ModVerify.App.Utilities; -using AET.ModVerify.Reporting; -using AET.ModVerify.Reporting.Reporters; -using AET.ModVerify.Reporting.Reporters.JSON; -using AET.ModVerify.Reporting.Reporters.Text; -using AET.ModVerify.Reporting.Settings; using AET.SteamAbstraction; using AnakinRaW.ApplicationBase; using AnakinRaW.ApplicationBase.Environment; @@ -36,7 +31,6 @@ using Serilog.Sinks.SystemConsole.Themes; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO.Abstractions; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -157,11 +151,10 @@ protected override void CreateAppServices(IServiceCollection services, IReadOnly PetroglyphCommons.ContributeServices(services); PetroglyphEngineServiceContribution.ContributeServices(services); + services.AddModVerify(); services.RegisterVerifierCache(); services.AddSingleton(sp => new BaselineFactory(sp)); - - SetupVerifyReporting(services); if (_offlineMode) { @@ -200,37 +193,6 @@ protected override async Task RunAppAsync(string[] args, IServiceProvider a return await new ModVerifyApplication(_modVerifyAppSettings, appServiceProvider).RunAsync().ConfigureAwait(false); } - private void SetupVerifyReporting(IServiceCollection serviceCollection) - { - Debug.Assert(_modVerifyAppSettings is not null); - - var verifySettings = _modVerifyAppSettings as AppVerifySettings; - - // Console should be in minimal summary mode if we are in a different mode than verify. - serviceCollection.RegisterConsoleReporter(new ReporterSettings - { - MinimumReportSeverity = verifySettings?.VerifyPipelineSettings.FailFastSettings.IsFailFast is true - ? VerificationSeverity.Information - : VerificationSeverity.Error - }, summaryOnly: verifySettings is null); - - if (verifySettings == null) - return; - - var outputDirectory = verifySettings.ReportDirectory; - serviceCollection.RegisterJsonReporter(new JsonReporterSettings - { - OutputDirectory = outputDirectory!, - MinimumReportSeverity = _modVerifyAppSettings.ReportSettings.MinimumReportSeverity - }); - - serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings - { - OutputDirectory = outputDirectory!, - MinimumReportSeverity = _modVerifyAppSettings.ReportSettings.MinimumReportSeverity - }); - } - private void ConfigureLogging(ILoggingBuilder loggingBuilder) { loggingBuilder.ClearProviders(); diff --git a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs index a83cea9..b621343 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs @@ -7,10 +7,9 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Abstractions; -using System.Linq; using System.Threading.Tasks; -using PG.StarWarsGame.Engine; using AET.ModVerify.App.Utilities; +using AET.ModVerify.Reporting.Baseline; namespace AET.ModVerify.App.Reporting; @@ -89,21 +88,13 @@ public VerificationBaseline CreateBaseline( Engine = target.Engine, Name = target.Name, Version = target.Version, - Location = settings.WriteLocations ? MaskUsername(target.Location) : null, + Location = settings.WriteLocations ? target.Location.MaskUsername() : null, IsGame = target.IsGame, }; return new VerificationBaseline(settings.ReportSettings.MinimumReportSeverity, errors, baselineTarget); } - private static GameLocations MaskUsername(GameLocations targetLocation) - { - return new GameLocations( - targetLocation.ModPaths.Select(PathUtilities.MaskUsername).ToList(), - PathUtilities.MaskUsername(targetLocation.GamePath), - targetLocation.FallbackPaths.Select(PathUtilities.MaskUsername).ToList()); - } - public async Task WriteBaselineAsync(VerificationBaseline baseline, string filePath) { #if NET diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs index 791eaae..efcaae5 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs @@ -1,6 +1,6 @@ using AET.ModVerify.App.Resources.Baselines; using AET.ModVerify.App.Settings; -using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; using AnakinRaW.ApplicationBase; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs index 721a7ce..a534cdb 100644 --- a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs @@ -1,5 +1,6 @@ using AET.ModVerify.App.Settings; using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs index b2ce170..66587ae 100644 --- a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs +++ b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs @@ -1,7 +1,7 @@ using System; using System.Threading; using AET.ModVerify.App.Settings; -using AET.ModVerify.Pipeline.Progress; +using AET.ModVerify.Progress; using AnakinRaW.CommonUtilities; using AnakinRaW.CommonUtilities.SimplePipeline.Progress; using ShellProgressBar; diff --git a/aet.ico b/src/ModVerify.CliApp/Resources/aet.ico similarity index 100% rename from aet.ico rename to src/ModVerify.CliApp/Resources/aet.ico diff --git a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs index e4488a0..add39a5 100644 --- a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs @@ -29,7 +29,7 @@ public required VerificationTargetSettings VerificationTargetSettings init => field = value ?? throw new ArgumentNullException(nameof(value)); } - public required VerifyPipelineSettings VerifyPipelineSettings + public required VerifierServiceSettings VerifierServiceSettings { get; init => field = value ?? throw new ArgumentNullException(nameof(value)); diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 23a89ff..62bca7a 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -1,5 +1,4 @@ using AET.ModVerify.App.Settings.CommandLine; -using AET.ModVerify.Pipeline; using AET.ModVerify.Settings; using Microsoft.Extensions.DependencyInjection; using System; @@ -31,7 +30,7 @@ private AppVerifySettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) return new AppVerifySettings(BuildReportSettings()) { ReportDirectory = GetReportDirectory(), - VerifyPipelineSettings = new VerifyPipelineSettings + VerifierServiceSettings = new VerifierServiceSettings { ParallelVerifiers = verifyOptions.Parallel ? 4 : 1, VerifiersProvider = new DefaultGameVerifiersProvider(), @@ -97,7 +96,7 @@ private AppBaselineSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOption { return new AppBaselineSettings(BuildReportSettings()) { - VerifyPipelineSettings = new VerifyPipelineSettings + VerifierServiceSettings = new VerifierServiceSettings { ParallelVerifiers = baselineVerb.Parallel ? 4 : 1, VerifiersProvider = new DefaultGameVerifiersProvider(), diff --git a/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs index 68f0f39..0c45986 100644 --- a/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs +++ b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; -using System.Runtime.InteropServices; using AET.ModVerify.App.GameFinder; using AET.ModVerify.App.Settings; using Microsoft.Extensions.DependencyInjection; diff --git a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs index fc07469..5871311 100644 --- a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs +++ b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs @@ -1,8 +1,9 @@ -using System.Diagnostics.CodeAnalysis; -using AET.ModVerify.App.Settings.CommandLine; +using AET.ModVerify.App.Settings.CommandLine; using AnakinRaW.ApplicationBase.Environment; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure.Games; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace AET.ModVerify.App.Utilities; @@ -21,10 +22,6 @@ public GameEngineType Opposite() } } - public static GameEngineType ToEngineType(this GameType type) - { - return (GameEngineType)(int)type; - } extension(ApplicationEnvironment modVerifyEnvironment) { public bool IsUpdatable() @@ -39,6 +36,19 @@ public bool IsUpdatable([NotNullWhen(true)] out UpdatableApplicationEnvironment? } } + public static GameEngineType ToEngineType(this GameType type) + { + return (GameEngineType)(int)type; + } + + public static GameLocations MaskUsername(this GameLocations targetLocation) + { + return new GameLocations( + targetLocation.ModPaths.Select(PathUtilities.MaskUsername).ToList(), + PathUtilities.MaskUsername(targetLocation.GamePath), + targetLocation.FallbackPaths.Select(PathUtilities.MaskUsername).ToList()); + } + public static bool LaunchedWithoutArguments(this BaseModVerifyOptions options) { if (options is VerifyVerbOption verifyOptions) diff --git a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs index b9ebff0..9f9962b 100644 --- a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs +++ b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs @@ -2,7 +2,7 @@ using Figgle; using System; using System.Collections.Generic; -using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; namespace AET.ModVerify.App.Utilities; diff --git a/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs b/src/ModVerify/DefaultGameVerifiersProvider.cs similarity index 96% rename from src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs rename to src/ModVerify/DefaultGameVerifiersProvider.cs index 26b3893..c9982b7 100644 --- a/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs +++ b/src/ModVerify/DefaultGameVerifiersProvider.cs @@ -5,7 +5,7 @@ using AET.ModVerify.Verifiers.GuiDialogs; using PG.StarWarsGame.Engine; -namespace AET.ModVerify.Pipeline; +namespace AET.ModVerify; public sealed class DefaultGameVerifiersProvider : IGameVerifiersProvider { diff --git a/src/ModVerify/GameVerificationException.cs b/src/ModVerify/GameVerificationException.cs index dba312b..a6ada92 100644 --- a/src/ModVerify/GameVerificationException.cs +++ b/src/ModVerify/GameVerificationException.cs @@ -7,8 +7,11 @@ namespace AET.ModVerify; public sealed class GameVerificationException : Exception { + /// + public override string Message => ErrorMessage; + public IReadOnlyCollection Errors { get; } - + private string ErrorMessage { get @@ -23,14 +26,11 @@ private string ErrorMessage } } = null; - /// - public override string Message => ErrorMessage; - - public GameVerificationException(VerificationError error) : this([error]) + internal GameVerificationException(VerificationError error) : this([error]) { } - public GameVerificationException(IEnumerable errors) + internal GameVerificationException(IEnumerable errors) { if (errors is null) throw new ArgumentNullException(nameof(errors)); diff --git a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs b/src/ModVerify/GameVerifierPipelineStep.cs similarity index 91% rename from src/ModVerify/Pipeline/GameVerifierPipelineStep.cs rename to src/ModVerify/GameVerifierPipelineStep.cs index e83cb25..b1f45b8 100644 --- a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs +++ b/src/ModVerify/GameVerifierPipelineStep.cs @@ -1,16 +1,16 @@ -using AET.ModVerify.Verifiers; +using System; +using System.Threading; +using System.Threading.Tasks; +using AET.ModVerify.Progress; +using AET.ModVerify.Verifiers; using AnakinRaW.CommonUtilities.SimplePipeline; using AnakinRaW.CommonUtilities.SimplePipeline.Progress; using AnakinRaW.CommonUtilities.SimplePipeline.Steps; using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; -using AET.ModVerify.Pipeline.Progress; -namespace AET.ModVerify.Pipeline; +namespace AET.ModVerify; -public sealed class GameVerifierPipelineStep( +internal sealed class GameVerifierPipelineStep( GameVerifier verifier, IServiceProvider serviceProvider) : PipelineStep(serviceProvider), IProgressStep diff --git a/src/ModVerify/GameVerifierService.cs b/src/ModVerify/GameVerifierService.cs new file mode 100644 index 0000000..f6d8f07 --- /dev/null +++ b/src/ModVerify/GameVerifierService.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AET.ModVerify.Progress; +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; +using AET.ModVerify.Reporting.Suppressions; +using AET.ModVerify.Settings; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify; + +internal sealed class GameVerifierService(IServiceProvider serviceProvider) : IGameVerifierService +{ + public async Task VerifyAsync( + VerificationTarget verificationTarget, + VerifierServiceSettings settings, + VerificationBaseline baseline, + SuppressionList suppressions, + IVerifyProgressReporter? progressReporter, + IGameEngineInitializationReporter? engineInitializationReporter, + CancellationToken token = default) + { + if (verificationTarget == null) + throw new ArgumentNullException(nameof(verificationTarget)); + if (settings == null) + throw new ArgumentNullException(nameof(settings)); + + using var pipeline = new GameVerifyPipeline( + verificationTarget, + settings, + serviceProvider, + baseline, + suppressions, + progressReporter, + engineInitializationReporter); + + VerificationCompletionStatus completionStatus; + var start = DateTime.UtcNow; + + try + { + await pipeline.RunAsync(token).ConfigureAwait(false); + completionStatus = VerificationCompletionStatus.Completed; + } + catch (OperationCanceledException) + { + completionStatus = settings.FailFastSettings.IsFailFast + ? VerificationCompletionStatus.CompletedFailFast + : VerificationCompletionStatus.Cancelled; + } + + var duration = DateTime.UtcNow - start; + + return new VerificationResult + { + Duration = duration, + Errors = pipeline.Errors, + Status = completionStatus, + Target = verificationTarget, + UsedBaseline = baseline, + UsedSuppressions = suppressions, + Verifiers = pipeline.Verifiers + }; + } +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/GameVerifyPipeline.cs similarity index 56% rename from src/ModVerify/Pipeline/GameVerifyPipeline.cs rename to src/ModVerify/GameVerifyPipeline.cs index 493aa8c..4ed351e 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/GameVerifyPipeline.cs @@ -1,63 +1,67 @@ -using AET.ModVerify.Pipeline.Progress; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AET.ModVerify.Progress; using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; +using AET.ModVerify.Reporting.Engine; +using AET.ModVerify.Reporting.Suppressions; using AET.ModVerify.Settings; +using AET.ModVerify.Utilities; using AET.ModVerify.Verifiers; using AnakinRaW.CommonUtilities.SimplePipeline; using AnakinRaW.CommonUtilities.SimplePipeline.Runners; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PG.StarWarsGame.Engine; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AET.ModVerify.Utilities; -using Microsoft.Extensions.DependencyInjection; -namespace AET.ModVerify.Pipeline; +namespace AET.ModVerify; -public sealed class GameVerifyPipeline : StepRunnerPipelineBase +internal sealed class GameVerifyPipeline : StepRunnerPipelineBase { private readonly List _verifiers = []; + private readonly List _errors = []; private readonly List _verificationSteps = []; - private readonly ConcurrentGameEngineErrorReporter _engineErrorReporter = new(); - + + private readonly GameEngineErrorCollection _engineErrorReporter = new(); private readonly VerificationTarget _verificationTarget; - private readonly VerifyPipelineSettings _pipelineSettings; - private readonly IVerifyProgressReporter _progressReporter; + private readonly VerifierServiceSettings _serviceSettings; + private readonly IVerifyProgressReporter? _progressReporter; private readonly IGameEngineInitializationReporter? _engineInitializationReporter; - private readonly IPetroglyphStarWarsGameEngineService _gameEngineService; - private readonly ILogger? _logger; - private AggregatedVerifyProgressReporter? _aggregatedVerifyProgressReporter; + private readonly VerificationBaseline _baseline; + private readonly SuppressionList _suppressions; + + internal IReadOnlyCollection Errors => [.._errors]; + + internal IReadOnlyCollection Verifiers => [.. _verifiers]; - public IReadOnlyCollection FilteredErrors { get; private set; } = []; - public VerificationBaseline Baseline { get; } - public SuppressionList Suppressions { get; } + private AggregatedVerifyProgressReporter? _aggregatedVerifyProgressReporter; public GameVerifyPipeline( VerificationTarget verificationTarget, - VerifyPipelineSettings pipelineSettings, - IVerifyProgressReporter progressReporter, - IGameEngineInitializationReporter? engineInitializationReporter, + VerifierServiceSettings serviceSettings, + IServiceProvider serviceProvider, VerificationBaseline baseline, SuppressionList suppressions, - IServiceProvider serviceProvider) : base(serviceProvider) + IVerifyProgressReporter? progressReporter = null, + IGameEngineInitializationReporter? engineInitializationReporter = null) + : base(serviceProvider) { - Baseline = baseline ?? throw new ArgumentNullException(nameof(baseline)); - Suppressions = suppressions ?? throw new ArgumentNullException(nameof(suppressions)); _verificationTarget = verificationTarget ?? throw new ArgumentNullException(nameof(verificationTarget)); - _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings)); - _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); + _serviceSettings = serviceSettings ?? throw new ArgumentNullException(nameof(serviceSettings)); + _baseline = baseline ?? throw new ArgumentNullException(nameof(baseline)); + _suppressions = suppressions ?? throw new ArgumentNullException(nameof(suppressions)); + _progressReporter = progressReporter; _engineInitializationReporter = engineInitializationReporter; - _gameEngineService = serviceProvider.GetRequiredService(); - _logger = serviceProvider.GetService()?.CreateLogger(GetType()); - FailFast = pipelineSettings.FailFastSettings.IsFailFast; + FailFast = serviceSettings.FailFastSettings.IsFailFast; } - + protected override AsyncStepRunner CreateRunner() { - var requestedRunnerCount = _pipelineSettings.ParallelVerifiers; + var requestedRunnerCount = _serviceSettings.ParallelVerifiers; return requestedRunnerCount switch { < 0 or > 64 => throw new InvalidOperationException( @@ -70,12 +74,14 @@ protected override AsyncStepRunner CreateRunner() protected override async Task PrepareCoreAsync(CancellationToken token) { _verifiers.Clear(); + _errors.Clear(); IStarWarsGameEngine gameEngine; try { - gameEngine = await _gameEngineService.InitializeAsync( + var engineService = ServiceProvider.GetRequiredService(); + gameEngine = await engineService.InitializeAsync( _verificationTarget.Engine, _verificationTarget.Location, _engineErrorReporter, @@ -85,38 +91,43 @@ protected override async Task PrepareCoreAsync(CancellationToken token) } catch (Exception e) { - _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); + Logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); throw; } - AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); + AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _serviceSettings.GameVerifySettings, ServiceProvider)); - foreach (var gameVerificationStep in CreateVerificationSteps(gameEngine)) + foreach (var gameVerificationStep in CreateVerifiers(gameEngine)) AddStep(gameVerificationStep); } protected override void OnExecuteStarted() { Logger?.LogInformation("Running game verifiers..."); - _aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); - _progressReporter.Report(0.0, $"Verifying {_verificationTarget.Name}...", VerifyProgress.ProgressType, default); + if (_progressReporter is not null) + { + _aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); + _progressReporter.Report(0.0, $"Verifying {_verificationTarget.Name}...", + VerifyProgress.ProgressType, default); + } } protected override void OnExecuteCompleted() { Logger?.LogInformation("Game verifiers finished."); - FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); - _progressReporter.Report(1.0, $"Finished Verifying {_verificationTarget.Name}", VerifyProgress.ProgressType, default); + _errors.AddRange(GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors))); + _progressReporter?.Report(1.0, $"Finished Verifying {_verificationTarget.Name}", + VerifyProgress.ProgressType, default); } protected override void OnRunnerExecutionError(object sender, StepRunnerErrorEventArgs e) { if (FailFast && e.Exception is GameVerificationException verificationException) { - var minSeverity = _pipelineSettings.FailFastSettings.MinumumSeverity; + var minSeverity = _serviceSettings.FailFastSettings.MinumumSeverity; var ignoreError = verificationException.Errors .Where(error => error.Severity >= minSeverity) - .All(error => Baseline.Contains(error) || Suppressions.Suppresses(error)); + .All(error => _baseline.Contains(error) || _suppressions.Suppresses(error)); if (ignoreError) return; } @@ -128,6 +139,14 @@ protected override IEnumerable GetFailedSteps(IEnumerable steps) return base.GetFailedSteps(steps).Where(s => s.Error is not GameVerificationException); } + protected override void DisposeResources() + { + base.DisposeResources(); + _engineErrorReporter.Clear(); + _aggregatedVerifyProgressReporter?.Dispose(); + _aggregatedVerifyProgressReporter = null; + } + private void AddStep(GameVerifier verifier) { var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); @@ -141,18 +160,13 @@ private IEnumerable GetReportableErrors(IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) - { - return _pipelineSettings.VerifiersProvider - .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); + return errors.ApplyBaseline(_baseline).ApplySuppressions(_suppressions); } - protected override void DisposeResources() + private IEnumerable CreateVerifiers(IStarWarsGameEngine engine) { - base.DisposeResources(); - _aggregatedVerifyProgressReporter?.Dispose(); + return _serviceSettings.VerifiersProvider + .GetVerifiers(engine, _serviceSettings.GameVerifySettings, ServiceProvider) + .Distinct(NameBasedEqualityComparer.Instance); } } \ No newline at end of file diff --git a/src/ModVerify/IGameVerifierService.cs b/src/ModVerify/IGameVerifierService.cs new file mode 100644 index 0000000..c814779 --- /dev/null +++ b/src/ModVerify/IGameVerifierService.cs @@ -0,0 +1,22 @@ +using System.Threading; +using System.Threading.Tasks; +using AET.ModVerify.Progress; +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; +using AET.ModVerify.Reporting.Suppressions; +using AET.ModVerify.Settings; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify; + +public interface IGameVerifierService +{ + Task VerifyAsync( + VerificationTarget verificationTarget, + VerifierServiceSettings settings, + VerificationBaseline baseline, + SuppressionList suppressions, + IVerifyProgressReporter? progressReporter, + IGameEngineInitializationReporter? engineInitializationReporter, + CancellationToken token = default); +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/IGameVerifiersProvider.cs b/src/ModVerify/IGameVerifiersProvider.cs similarity index 91% rename from src/ModVerify/Pipeline/IGameVerifiersProvider.cs rename to src/ModVerify/IGameVerifiersProvider.cs index 930afa6..8cde9dc 100644 --- a/src/ModVerify/Pipeline/IGameVerifiersProvider.cs +++ b/src/ModVerify/IGameVerifiersProvider.cs @@ -4,7 +4,7 @@ using AET.ModVerify.Verifiers; using PG.StarWarsGame.Engine; -namespace AET.ModVerify.Pipeline; +namespace AET.ModVerify; public interface IGameVerifiersProvider { diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index f1415bc..aecf306 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -10,33 +10,25 @@ ModVerify Core AET.ModVerify - Provides interfaces and classes to verify Empire at War / Forces of Corruption game modifications. + A library that contains classes and methods to verify Empire at War / Forces of Corruption game modifications. alamo,petroglyph,glyphx true true - - - true snupkg - - - - - - + @@ -50,9 +42,4 @@ - - - - - diff --git a/src/ModVerify/ModVerify.csproj.DotSettings b/src/ModVerify/ModVerify.csproj.DotSettings index fcd6f14..3859842 100644 --- a/src/ModVerify/ModVerify.csproj.DotSettings +++ b/src/ModVerify/ModVerify.csproj.DotSettings @@ -1,4 +1,9 @@  + True + True + True + True + False True True True \ No newline at end of file diff --git a/src/ModVerify/ModVerifyServiceExtensions.cs b/src/ModVerify/ModVerifyServiceExtensions.cs index 8b46960..41fbeaa 100644 --- a/src/ModVerify/ModVerifyServiceExtensions.cs +++ b/src/ModVerify/ModVerifyServiceExtensions.cs @@ -5,8 +5,16 @@ namespace AET.ModVerify; public static class ModVerifyServiceExtensions { - public static IServiceCollection RegisterVerifierCache(this IServiceCollection serviceCollection) + extension(IServiceCollection serviceCollection) { - return serviceCollection.AddSingleton(sp => new AlreadyVerifiedCache(sp)); + public IServiceCollection AddModVerify() + { + return serviceCollection.AddSingleton(sp => new GameVerifierService(sp)); + } + + public IServiceCollection RegisterVerifierCache() + { + return serviceCollection.AddSingleton(sp => new AlreadyVerifiedCache(sp)); + } } } \ No newline at end of file diff --git a/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs b/src/ModVerify/Progress/AggregatedVerifyProgressReporter.cs similarity index 95% rename from src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs rename to src/ModVerify/Progress/AggregatedVerifyProgressReporter.cs index cfdae25..1dbf538 100644 --- a/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs +++ b/src/ModVerify/Progress/AggregatedVerifyProgressReporter.cs @@ -1,8 +1,8 @@ -using AnakinRaW.CommonUtilities.SimplePipeline.Progress; -using System; +using System; using System.Collections.Generic; +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; -namespace AET.ModVerify.Pipeline.Progress; +namespace AET.ModVerify.Progress; internal class AggregatedVerifyProgressReporter( IVerifyProgressReporter progressReporter, diff --git a/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs b/src/ModVerify/Progress/IVerifyProgressReporter.cs similarity index 76% rename from src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs rename to src/ModVerify/Progress/IVerifyProgressReporter.cs index e63e0f9..99c2141 100644 --- a/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs +++ b/src/ModVerify/Progress/IVerifyProgressReporter.cs @@ -1,5 +1,5 @@ using AnakinRaW.CommonUtilities.SimplePipeline.Progress; -namespace AET.ModVerify.Pipeline.Progress; +namespace AET.ModVerify.Progress; public interface IVerifyProgressReporter : IProgressReporter; \ No newline at end of file diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgress.cs b/src/ModVerify/Progress/VerifyProgress.cs similarity index 84% rename from src/ModVerify/Pipeline/Progress/VerifyProgress.cs rename to src/ModVerify/Progress/VerifyProgress.cs index a18f3d3..aa5f2f2 100644 --- a/src/ModVerify/Pipeline/Progress/VerifyProgress.cs +++ b/src/ModVerify/Progress/VerifyProgress.cs @@ -1,6 +1,6 @@ using AnakinRaW.CommonUtilities.SimplePipeline.Progress; -namespace AET.ModVerify.Pipeline.Progress; +namespace AET.ModVerify.Progress; public static class VerifyProgress { diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs b/src/ModVerify/Progress/VerifyProgressInfo.cs similarity index 74% rename from src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs rename to src/ModVerify/Progress/VerifyProgressInfo.cs index cadeb0d..b7c8b3d 100644 --- a/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs +++ b/src/ModVerify/Progress/VerifyProgressInfo.cs @@ -1,4 +1,4 @@ -namespace AET.ModVerify.Pipeline.Progress; +namespace AET.ModVerify.Progress; public struct VerifyProgressInfo { diff --git a/src/ModVerify/Reporting/Baseline/BaselineVerificationTarget.cs b/src/ModVerify/Reporting/Baseline/BaselineVerificationTarget.cs new file mode 100644 index 0000000..ca77fa8 --- /dev/null +++ b/src/ModVerify/Reporting/Baseline/BaselineVerificationTarget.cs @@ -0,0 +1,12 @@ +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Reporting.Baseline; + +public sealed record BaselineVerificationTarget +{ + public required GameEngineType Engine { get; init; } + public required string Name { get; init; } + public GameLocations? Location { get; init; } // Optional compared to Verification Target + public string? Version { get; init; } + public bool IsGame { get; init; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Baseline/InvalidBaselineException.cs b/src/ModVerify/Reporting/Baseline/InvalidBaselineException.cs new file mode 100644 index 0000000..58e180e --- /dev/null +++ b/src/ModVerify/Reporting/Baseline/InvalidBaselineException.cs @@ -0,0 +1,14 @@ +using System; + +namespace AET.ModVerify.Reporting.Baseline; + +public sealed class InvalidBaselineException : Exception +{ + internal InvalidBaselineException(string message) : base(message) + { + } + + internal InvalidBaselineException(string? message, Exception? inner) : base(message, inner) + { + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs b/src/ModVerify/Reporting/Baseline/Json/JsonBaselineParser.cs similarity index 95% rename from src/ModVerify/Reporting/Json/JsonBaselineParser.cs rename to src/ModVerify/Reporting/Baseline/Json/JsonBaselineParser.cs index 669ef53..a8ba53c 100644 --- a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs +++ b/src/ModVerify/Reporting/Baseline/Json/JsonBaselineParser.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text.Json; -namespace AET.ModVerify.Reporting.Json; +namespace AET.ModVerify.Reporting.Baseline.Json; internal static class JsonBaselineParser { diff --git a/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs b/src/ModVerify/Reporting/Baseline/Json/JsonBaselineSchema.cs similarity index 97% rename from src/ModVerify/Reporting/Json/JsonBaselineSchema.cs rename to src/ModVerify/Reporting/Baseline/Json/JsonBaselineSchema.cs index 12e3705..cf4513a 100644 --- a/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs +++ b/src/ModVerify/Reporting/Baseline/Json/JsonBaselineSchema.cs @@ -7,9 +7,9 @@ using Json.Schema; using Json.Schema.Keywords; -namespace AET.ModVerify.Reporting.Json; +namespace AET.ModVerify.Reporting.Baseline.Json; -public static class JsonBaselineSchema +internal static class JsonBaselineSchema { private static readonly JsonSchema Schema; private static readonly EvaluationOptions EvaluationOptions; diff --git a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs b/src/ModVerify/Reporting/Baseline/Json/JsonVerificationBaseline.cs similarity index 92% rename from src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs rename to src/ModVerify/Reporting/Baseline/Json/JsonVerificationBaseline.cs index 0d9a1b7..3e25aed 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs +++ b/src/ModVerify/Reporting/Baseline/Json/JsonVerificationBaseline.cs @@ -1,9 +1,10 @@ -using System; +using AET.ModVerify.Reporting.Json; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; -namespace AET.ModVerify.Reporting.Json; +namespace AET.ModVerify.Reporting.Baseline.Json; internal class JsonVerificationBaseline { diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/Baseline/VerificationBaseline.cs similarity index 96% rename from src/ModVerify/Reporting/VerificationBaseline.cs rename to src/ModVerify/Reporting/Baseline/VerificationBaseline.cs index 3f9c274..d734e6e 100644 --- a/src/ModVerify/Reporting/VerificationBaseline.cs +++ b/src/ModVerify/Reporting/Baseline/VerificationBaseline.cs @@ -6,9 +6,10 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; +using AET.ModVerify.Reporting.Baseline.Json; using AET.ModVerify.Reporting.Json; -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Baseline; public sealed class VerificationBaseline : IReadOnlyCollection { diff --git a/src/ModVerify/Reporting/BaselineVerificationTarget.cs b/src/ModVerify/Reporting/BaselineVerificationTarget.cs deleted file mode 100644 index 6e21ddd..0000000 --- a/src/ModVerify/Reporting/BaselineVerificationTarget.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text; -using PG.StarWarsGame.Engine; - -namespace AET.ModVerify.Reporting; - -public sealed class BaselineVerificationTarget -{ - public required GameEngineType Engine { get; init; } - public required string Name { get; init; } - public GameLocations? Location { get; init; } // Optional compared to Verification Target - public string? Version { get; init; } - public bool IsGame { get; init; } - - public override string ToString() - { - var sb = new StringBuilder($"[Name={Name};EngineType={Engine};IsGame={IsGame};"); - if (!string.IsNullOrEmpty(Version)) sb.Append($"Version={Version};"); - if (Location is not null) - sb.Append($"Location={Location};"); - sb.Append(']'); - return sb.ToString(); - } -} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs b/src/ModVerify/Reporting/Engine/EngineErrorReporterBase.cs similarity index 83% rename from src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs rename to src/ModVerify/Reporting/Engine/EngineErrorReporterBase.cs index 455212d..7200f23 100644 --- a/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs +++ b/src/ModVerify/Reporting/Engine/EngineErrorReporterBase.cs @@ -1,16 +1,22 @@ using System; using System.Collections.Generic; +using AET.ModVerify.Verifiers; using AnakinRaW.CommonUtilities; using PG.StarWarsGame.Engine.IO; -namespace AET.ModVerify.Reporting.Reporters.Engine; +namespace AET.ModVerify.Reporting.Engine; -internal abstract class EngineErrorReporterBase(IGameRepository gameRepository, IServiceProvider serviceProvider) +internal abstract class EngineErrorReporterBase(IGameRepository gameRepository, IServiceProvider serviceProvider) + : IGameVerifierInfo { protected readonly IGameRepository GameRepository = gameRepository ?? throw new ArgumentNullException(nameof(gameRepository)); protected readonly IServiceProvider ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - public abstract string Name { get; } + public IGameVerifierInfo? Parent => null; + + public string Name => GetType().FullName; + + public abstract string FriendlyName { get; } public IEnumerable GetErrors(IEnumerable errors) { @@ -18,7 +24,7 @@ public IEnumerable GetErrors(IEnumerable errors) { var errorData = CreateError(error); yield return new VerificationError( - errorData.Identifier, errorData.Message, [Name], errorData.Context, errorData.Asset, errorData.Severity); + errorData.Identifier, errorData.Message, [this], errorData.Context, errorData.Asset, errorData.Severity); } } diff --git a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs b/src/ModVerify/Reporting/Engine/GameAssertErrorReporter.cs similarity index 93% rename from src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs rename to src/ModVerify/Reporting/Engine/GameAssertErrorReporter.cs index 6c5fb17..f75334c 100644 --- a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs +++ b/src/ModVerify/Reporting/Engine/GameAssertErrorReporter.cs @@ -5,12 +5,12 @@ using PG.StarWarsGame.Engine.ErrorReporting; using PG.StarWarsGame.Engine.IO; -namespace AET.ModVerify.Reporting.Reporters.Engine; +namespace AET.ModVerify.Reporting.Engine; internal sealed class GameAssertErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) : EngineErrorReporterBase(gameRepository, serviceProvider) { - public override string Name => "GameAsserts"; + public override string FriendlyName => "Game Engine Asserts"; protected override ErrorData CreateError(EngineAssert assert) { diff --git a/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs b/src/ModVerify/Reporting/Engine/GameEngineErrorCollection.cs similarity index 50% rename from src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs rename to src/ModVerify/Reporting/Engine/GameEngineErrorCollection.cs index 3130547..ba0abf5 100644 --- a/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs +++ b/src/ModVerify/Reporting/Engine/GameEngineErrorCollection.cs @@ -3,9 +3,9 @@ using System.Linq; using PG.StarWarsGame.Engine.ErrorReporting; -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Engine; -public sealed class ConcurrentGameEngineErrorReporter : GameEngineErrorReporter, IGameEngineErrorCollection +public sealed class GameEngineErrorCollection : IGameEngineErrorCollection, IGameEngineErrorReporter { private readonly ConcurrentBag _xmlErrors = new(); private readonly ConcurrentBag _initializationErrors = new(); @@ -17,18 +17,36 @@ public sealed class ConcurrentGameEngineErrorReporter : GameEngineErrorReporter, public IEnumerable Asserts => _asserts.ToList(); - public override void Report(XmlError error) + void IGameEngineErrorReporter.Report(XmlError error) { _xmlErrors.Add(error); } - public override void Report(InitializationError error) + void IGameEngineErrorReporter.Report(InitializationError error) { _initializationErrors.Add(error); } - public override void Assert(EngineAssert assert) + void IGameEngineErrorReporter.Assert(EngineAssert assert) { _asserts.Add(assert); } + + internal void Clear() + { +#if !NETFRAMEWORK && !NETSTANDARD2_0 + _xmlErrors.Clear(); + _initializationErrors.Clear(); + _asserts.Clear(); +#else + ClearUnsafe(_xmlErrors); + ClearUnsafe(_initializationErrors); + ClearUnsafe(_asserts); + + static void ClearUnsafe(ConcurrentBag bag) + { + while (bag.TryTake(out _)) ; + } +#endif + } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/IGameEngineErrorCollection.cs b/src/ModVerify/Reporting/Engine/IGameEngineErrorCollection.cs similarity index 87% rename from src/ModVerify/Reporting/IGameEngineErrorCollection.cs rename to src/ModVerify/Reporting/Engine/IGameEngineErrorCollection.cs index 14d59d9..adf734d 100644 --- a/src/ModVerify/Reporting/IGameEngineErrorCollection.cs +++ b/src/ModVerify/Reporting/Engine/IGameEngineErrorCollection.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using PG.StarWarsGame.Engine.ErrorReporting; -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Engine; public interface IGameEngineErrorCollection { diff --git a/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs b/src/ModVerify/Reporting/Engine/InitializationErrorReporter.cs similarity index 84% rename from src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs rename to src/ModVerify/Reporting/Engine/InitializationErrorReporter.cs index fc44c66..e46db1a 100644 --- a/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs +++ b/src/ModVerify/Reporting/Engine/InitializationErrorReporter.cs @@ -3,12 +3,12 @@ using PG.StarWarsGame.Engine.ErrorReporting; using PG.StarWarsGame.Engine.IO; -namespace AET.ModVerify.Reporting.Reporters.Engine; +namespace AET.ModVerify.Reporting.Engine; internal sealed class InitializationErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) : EngineErrorReporterBase(gameRepository, serviceProvider) { - public override string Name => "InitializationErrors"; + public override string FriendlyName => "Initialization Errors"; protected override ErrorData CreateError(InitializationError error) { diff --git a/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs b/src/ModVerify/Reporting/Engine/XmlParseErrorReporter.cs similarity index 97% rename from src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs rename to src/ModVerify/Reporting/Engine/XmlParseErrorReporter.cs index 094a701..2629f26 100644 --- a/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs +++ b/src/ModVerify/Reporting/Engine/XmlParseErrorReporter.cs @@ -8,14 +8,14 @@ using PG.StarWarsGame.Engine.IO; using PG.StarWarsGame.Files.XML.ErrorHandling; -namespace AET.ModVerify.Reporting.Reporters.Engine; +namespace AET.ModVerify.Reporting.Engine; internal sealed class XmlParseErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) : EngineErrorReporterBase(gameRepository, serviceProvider) { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public override string Name => "XMLError"; + public override string FriendlyName => "XML Errors"; protected override ErrorData CreateError(XmlError error) { diff --git a/src/ModVerify/Reporting/IVerificationReporter.cs b/src/ModVerify/Reporting/IVerificationReporter.cs deleted file mode 100644 index 8b8664e..0000000 --- a/src/ModVerify/Reporting/IVerificationReporter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace AET.ModVerify.Reporting; - -public interface IVerificationReporter -{ - public Task ReportAsync(IReadOnlyCollection errors); -} \ No newline at end of file diff --git a/src/ModVerify/Reporting/InvalidBaselineException.cs b/src/ModVerify/Reporting/InvalidBaselineException.cs deleted file mode 100644 index 37ab9c8..0000000 --- a/src/ModVerify/Reporting/InvalidBaselineException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace AET.ModVerify.Reporting; - -public sealed class InvalidBaselineException : Exception -{ - public InvalidBaselineException(string message) : base(message) - { - } - - public InvalidBaselineException(string? message, Exception? inner) : base(message, inner) - { - } -} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonAggregatedVerificationError.cs b/src/ModVerify/Reporting/Json/JsonAggregatedVerificationError.cs new file mode 100644 index 0000000..5a9cbb3 --- /dev/null +++ b/src/ModVerify/Reporting/Json/JsonAggregatedVerificationError.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace AET.ModVerify.Reporting.Json; + +internal class JsonAggregatedVerificationError : JsonVerificationErrorBase +{ + [JsonPropertyName("contexts")] + [JsonPropertyOrder(99)] + public IEnumerable> Contexts { get; } + + [JsonConstructor] + public JsonAggregatedVerificationError( + string id, + IReadOnlyList? verifierChain, + string message, + VerificationSeverity severity, + IEnumerable>? contexts, + string? asset) : base(id, verifierChain, message, severity, asset) + { + Contexts = contexts ?? []; + } + + public JsonAggregatedVerificationError( + VerificationError error, + IEnumerable> contexts) : base(error) + { + Contexts = contexts; + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationError.cs b/src/ModVerify/Reporting/Json/JsonVerificationError.cs index 55f7b92..0e23283 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationError.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationError.cs @@ -1,53 +1,29 @@ using System.Collections.Generic; +using System.Linq; using System.Text.Json.Serialization; namespace AET.ModVerify.Reporting.Json; -internal class JsonVerificationError +internal class JsonVerificationError : JsonVerificationErrorBase { - [JsonPropertyName("id")] - public string Id { get; } - - [JsonPropertyName("verifiers")] - public IReadOnlyList VerifierChain { get; } - - [JsonPropertyName("message")] - public string Message { get; } - - [JsonPropertyName("severity")] - [JsonConverter(typeof(JsonStringEnumConverter))] - public VerificationSeverity Severity { get; } - [JsonPropertyName("context")] - public IEnumerable ContextEntries { get; } - - [JsonPropertyName("asset")] - public string Asset { get; } + [JsonPropertyOrder(99)] + public IEnumerable? ContextEntries { get; } [JsonConstructor] - private JsonVerificationError( + public JsonVerificationError( string id, IReadOnlyList? verifierChain, string message, VerificationSeverity severity, IEnumerable? contextEntries, - string? asset) + string? asset) : base(id, verifierChain, message, severity, asset) { - Id = id; - VerifierChain = verifierChain ?? []; - Message = message; - Severity = severity; - ContextEntries = contextEntries ?? []; - Asset = asset ?? string.Empty; + ContextEntries = contextEntries; } - public JsonVerificationError(VerificationError error) + public JsonVerificationError(VerificationError error) : base(error) { - Id = error.Id; - VerifierChain = error.VerifierChain; - Message = error.Message; - Severity = error.Severity; - ContextEntries = error.ContextEntries; - Asset = error.Asset; + ContextEntries = error.ContextEntries.Any() ? error.ContextEntries : null; } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationErrorBase.cs b/src/ModVerify/Reporting/Json/JsonVerificationErrorBase.cs new file mode 100644 index 0000000..06af012 --- /dev/null +++ b/src/ModVerify/Reporting/Json/JsonVerificationErrorBase.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; + +namespace AET.ModVerify.Reporting.Json; + +[JsonDerivedType(typeof(JsonVerificationError))] +[JsonDerivedType(typeof(JsonAggregatedVerificationError))] +internal abstract class JsonVerificationErrorBase +{ + [JsonPropertyName("id")] + public string Id { get; } + + [JsonPropertyName("verifiers")] + public IReadOnlyList VerifierChain { get; } + + [JsonPropertyName("message")] + public string Message { get; } + + [JsonPropertyName("severity")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public VerificationSeverity Severity { get; } + + [JsonPropertyName("asset")] + public string Asset { get; } + + protected JsonVerificationErrorBase( + string id, + IReadOnlyList? verifierChain, + string message, + VerificationSeverity severity, + string? asset) + { + Id = id; + VerifierChain = verifierChain ?? []; + Message = message; + Severity = severity; + Asset = asset ?? string.Empty; + } + + protected JsonVerificationErrorBase(VerificationError error) + { + Id = error.Id; + VerifierChain = error.VerifierChain.Select(x => x.Name).ToList(); + Message = error.Message; + Severity = error.Severity; + Asset = error.Asset; + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationReport.cs b/src/ModVerify/Reporting/Json/JsonVerificationReport.cs index 6ebb926..d10d18f 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationReport.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationReport.cs @@ -3,8 +3,11 @@ namespace AET.ModVerify.Reporting.Json; -internal class JsonVerificationReport(IEnumerable errors) +internal class JsonVerificationReport { + [JsonPropertyName("metadata")] + public required JsonVerificationReportMetadata Metadata { get; init; } + [JsonPropertyName("errors")] - public IEnumerable Errors { get; } = errors; + public required IEnumerable Errors { get; init; } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationReportMetadata.cs b/src/ModVerify/Reporting/Json/JsonVerificationReportMetadata.cs new file mode 100644 index 0000000..7e3b90e --- /dev/null +++ b/src/ModVerify/Reporting/Json/JsonVerificationReportMetadata.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace AET.ModVerify.Reporting.Json; + +internal class JsonVerificationReportMetadata +{ + [JsonPropertyName("status")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public VerificationCompletionStatus Status { get; init; } + + [JsonPropertyName("target")] + public JsonVerificationTarget Target { get; init; } + + [JsonPropertyName("time")] + public string Date { get; } = DateTime.Now.ToString("s"); + + [JsonPropertyName("duration")] + public string Duration { get; init; } + + [JsonPropertyName("verifiers")] + public IReadOnlyCollection Verifiers { get; init; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs index 9495456..fe7e33d 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using AET.ModVerify.Reporting.Baseline; using PG.StarWarsGame.Engine; namespace AET.ModVerify.Reporting.Json; @@ -38,6 +39,15 @@ private JsonVerificationTarget( IsGame = isGame; } + public JsonVerificationTarget(VerificationTarget target) + { + Name = target.Name; + Version = target.Version; + Engine = target.Engine; + Location = new JsonGameLocation(target.Location); + IsGame = target.IsGame; + } + public JsonVerificationTarget(BaselineVerificationTarget target) { Name = target.Name; diff --git a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs b/src/ModVerify/Reporting/Reporters/Console/ConsoleReporter.cs similarity index 62% rename from src/ModVerify/Reporting/Reporters/ConsoleReporter.cs rename to src/ModVerify/Reporting/Reporters/Console/ConsoleReporter.cs index dbee50d..5266550 100644 --- a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs +++ b/src/ModVerify/Reporting/Reporters/Console/ConsoleReporter.cs @@ -2,25 +2,21 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AET.ModVerify.Reporting.Settings; namespace AET.ModVerify.Reporting.Reporters; -internal class ConsoleReporter( - ReporterSettings settings, - bool summaryOnly, - IServiceProvider serviceProvider) : - ReporterBase(settings, serviceProvider) +internal class ConsoleReporter(ConsoleReporterSettings settings, IServiceProvider serviceProvider) : + ReporterBase(settings, serviceProvider) { - public override Task ReportAsync(IReadOnlyCollection errors) + public override Task ReportAsync(VerificationResult verificationResult) { - var filteredErrors = FilteredErrors(errors).OrderByDescending(x => x.Severity).ToList(); - PrintErrorStats(errors, filteredErrors); + var filteredErrors = FilteredErrors(verificationResult.Errors).OrderByDescending(x => x.Severity).ToList(); + PrintErrorStats(verificationResult, filteredErrors); Console.WriteLine(); return Task.CompletedTask; } - private void PrintErrorStats(IReadOnlyCollection errors, List filteredErrors) + private void PrintErrorStats(VerificationResult verificationResult, List filteredErrors) { Console.WriteLine(); Console.WriteLine(); @@ -28,9 +24,9 @@ private void PrintErrorStats(IReadOnlyCollection errors, List Console.WriteLine(" Error Report "); Console.WriteLine("***********************"); Console.WriteLine(); - if (errors.Count == 0) + if (verificationResult.Errors.Count == 0) { - if (summaryOnly) + if (Settings.SummaryOnly) { Console.WriteLine("No errors found."); } @@ -44,21 +40,21 @@ private void PrintErrorStats(IReadOnlyCollection errors, List return; } - Console.WriteLine($"TOTAL Verification Errors: {errors.Count}"); + Console.WriteLine($"TOTAL Verification Errors: {verificationResult.Errors.Count}"); - var groupedBySeverity = errors.GroupBy(x => x.Severity); + var groupedBySeverity = verificationResult.Errors.GroupBy(x => x.Severity); foreach (var group in groupedBySeverity) Console.WriteLine($" Severity {group.Key}: {group.Count()}"); Console.WriteLine(); if (filteredErrors.Count == 0) { - if (errors.Count != 0) + if (verificationResult.Errors.Count != 0) Console.WriteLine("Some errors are not displayed to the console. Please check the created output files."); return; } - if (summaryOnly) + if (Settings.SummaryOnly) return; Console.WriteLine($"Below the list of errors with severity '{Settings.MinimumReportSeverity}' or higher:"); diff --git a/src/ModVerify/Reporting/Reporters/Console/ConsoleReporterSettings.cs b/src/ModVerify/Reporting/Reporters/Console/ConsoleReporterSettings.cs new file mode 100644 index 0000000..4dc2a77 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/Console/ConsoleReporterSettings.cs @@ -0,0 +1,6 @@ +namespace AET.ModVerify.Reporting.Reporters; + +public sealed record ConsoleReporterSettings : ReporterSettings +{ + public bool SummaryOnly { get; init; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/ExtensionMethods.cs b/src/ModVerify/Reporting/Reporters/ExtensionMethods.cs new file mode 100644 index 0000000..480e956 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/ExtensionMethods.cs @@ -0,0 +1,44 @@ +using System; + +namespace AET.ModVerify.Reporting.Reporters; + +public static class ExtensionMethods +{ + extension(IVerificationReporter) + { + public static IVerificationReporter CreateJson(IServiceProvider serviceProvider) + { + return IVerificationReporter.CreateJson(new JsonReporterSettings(), serviceProvider); + } + + public static IVerificationReporter CreateJson(JsonReporterSettings settings, IServiceProvider serviceProvider) + { + return new JsonReporter(settings, serviceProvider); + } + + public static IVerificationReporter CreateText(IServiceProvider serviceProvider) + { + return IVerificationReporter.CreateText(new TextFileReporterSettings(), serviceProvider); + } + + public static IVerificationReporter CreateText(TextFileReporterSettings settings, IServiceProvider serviceProvider) + { + return new TextFileReporter(settings, serviceProvider); + } + + public static IVerificationReporter CreateConsole(IServiceProvider serviceProvider, bool summaryOnly = false) + { + var settings = new ConsoleReporterSettings + { + MinimumReportSeverity = VerificationSeverity.Error, + SummaryOnly = summaryOnly + }; + return IVerificationReporter.CreateConsole(settings, serviceProvider); + } + + public static IVerificationReporter CreateConsole(ConsoleReporterSettings settings, IServiceProvider serviceProvider) + { + return new ConsoleReporter(settings, serviceProvider); + } + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs index 1057bfa..4c60121 100644 --- a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs +++ b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.IO.Abstractions; -using AET.ModVerify.Reporting.Settings; using Microsoft.Extensions.DependencyInjection; namespace AET.ModVerify.Reporting.Reporters; diff --git a/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs b/src/ModVerify/Reporting/Reporters/FileBasedReporterSettings.cs similarity index 85% rename from src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs rename to src/ModVerify/Reporting/Reporters/FileBasedReporterSettings.cs index 759a6ab..aa468fc 100644 --- a/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs +++ b/src/ModVerify/Reporting/Reporters/FileBasedReporterSettings.cs @@ -1,6 +1,6 @@ using System; -namespace AET.ModVerify.Reporting.Settings; +namespace AET.ModVerify.Reporting.Reporters; public record FileBasedReporterSettings : ReporterSettings { diff --git a/src/ModVerify/Reporting/Reporters/IVerificationReporter.cs b/src/ModVerify/Reporting/Reporters/IVerificationReporter.cs new file mode 100644 index 0000000..5ce7a65 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/IVerificationReporter.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace AET.ModVerify.Reporting.Reporters; + +public interface IVerificationReporter +{ + public Task ReportAsync(VerificationResult verificationResult); +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs index 348fbb1..1feb655 100644 --- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs +++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs @@ -4,21 +4,88 @@ using System.Text.Json; using System.Threading.Tasks; using AET.ModVerify.Reporting.Json; +using AET.ModVerify.Verifiers; +using AnakinRaW.CommonUtilities.FileSystem.Validation; -namespace AET.ModVerify.Reporting.Reporters.JSON; +namespace AET.ModVerify.Reporting.Reporters; internal class JsonReporter(JsonReporterSettings settings, IServiceProvider serviceProvider) : FileBasedReporter(settings, serviceProvider) { - public const string FileName = "VerificationResult.json"; - - public override async Task ReportAsync(IReadOnlyCollection errors) + public override async Task ReportAsync(VerificationResult verificationResult) { - var report = new JsonVerificationReport(errors.Select(x => new JsonVerificationError(x))); + var report = CreateJsonReport(verificationResult); + var fileName = CreateFileName(verificationResult); + #if NET || NETSTANDARD2_1 await #endif - using var fs = CreateFile(FileName); + using var fs = CreateFile(fileName); await JsonSerializer.SerializeAsync(fs, report, ModVerifyJsonSettings.JsonSettings); } + + private JsonVerificationReport CreateJsonReport(VerificationResult result) + { + IEnumerable errors; + if (Settings.AggregateResults) + { + errors = result.Errors + .GroupBy(x => new GroupKey(x.Asset, x.Id, x.VerifierChain)) + .Select, JsonVerificationErrorBase>(g => + { + var first = g.First(); + var contexts = g.Select(x => x.ContextEntries).ToList(); + + if (contexts.Count == 1) + return new JsonVerificationError(first); + return new JsonAggregatedVerificationError(first, contexts); + }); + } + else + { + errors = result.Errors.Select(x => new JsonVerificationError(x)); + } + + return new JsonVerificationReport + { + Metadata = new JsonVerificationReportMetadata + { + Target = new JsonVerificationTarget(result.Target), + Duration = result.Duration.ToString("g"), + Status = result.Status, + Verifiers = result.Verifiers.Select(x => x.Name).ToList() + }, + Errors = errors + }; + } + + private static string CreateFileName(VerificationResult result) + { + var fileName = $"VerificationResult_{result.Target.Name}.json"; + if (CurrentSystemFileNameValidator.Instance.IsValidFileName(fileName) is FileNameValidationResult.Valid) + return fileName; + // I don't think there is a safe/secure way to re-encode the file name, if it's not valid using the plain target name. + // Thus, we simply use the current date and accept the fact that files may get overwritten for different targets. + return $"VerificationResult_{DateTime.Now:yyyy_mm_dd}.json"; + + } + + private readonly record struct GroupKey(string Asset, string Id, IReadOnlyList VerifierChain) + { + public bool Equals(GroupKey other) + { + return Asset == other.Asset + && Id == other.Id + && VerifierChainEqualityComparer.Instance.Equals(VerifierChain, other.VerifierChain); + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(Asset); + hashCode.Add(Id); + hashCode.Add(VerifierChain, VerifierChainEqualityComparer.Instance); + return hashCode.ToHashCode(); + } + } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs index 4207b36..7ccbba9 100644 --- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs +++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs @@ -1,5 +1,6 @@ -using AET.ModVerify.Reporting.Settings; +namespace AET.ModVerify.Reporting.Reporters; -namespace AET.ModVerify.Reporting.Reporters.JSON; - -public record JsonReporterSettings : FileBasedReporterSettings; \ No newline at end of file +public record JsonReporterSettings : FileBasedReporterSettings +{ + public bool AggregateResults { get; init; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/ReporterBase.cs b/src/ModVerify/Reporting/Reporters/ReporterBase.cs index df444e3..47cafc5 100644 --- a/src/ModVerify/Reporting/Reporters/ReporterBase.cs +++ b/src/ModVerify/Reporting/Reporters/ReporterBase.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AET.ModVerify.Reporting.Settings; namespace AET.ModVerify.Reporting.Reporters; @@ -12,7 +11,7 @@ public abstract class ReporterBase(T settings, IServiceProvider serviceProvid protected T Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings)); - public abstract Task ReportAsync(IReadOnlyCollection errors); + public abstract Task ReportAsync(VerificationResult verificationResult); protected IEnumerable FilteredErrors(IReadOnlyCollection errors) { diff --git a/src/ModVerify/Reporting/Settings/ReporterSettings.cs b/src/ModVerify/Reporting/Reporters/ReporterSettings.cs similarity index 74% rename from src/ModVerify/Reporting/Settings/ReporterSettings.cs rename to src/ModVerify/Reporting/Reporters/ReporterSettings.cs index ef33857..73332eb 100644 --- a/src/ModVerify/Reporting/Settings/ReporterSettings.cs +++ b/src/ModVerify/Reporting/Reporters/ReporterSettings.cs @@ -1,4 +1,4 @@ -namespace AET.ModVerify.Reporting.Settings; +namespace AET.ModVerify.Reporting.Reporters; public record ReporterSettings { diff --git a/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs b/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs index cfc5c91..f3e8126 100644 --- a/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs +++ b/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs @@ -1,49 +1,56 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; -namespace AET.ModVerify.Reporting.Reporters.Text; +namespace AET.ModVerify.Reporting.Reporters; -internal class TextFileReporter(TextFileReporterSettings settings, IServiceProvider serviceProvider) : FileBasedReporter(settings, serviceProvider) +internal sealed class TextFileReporter(TextFileReporterSettings settings, IServiceProvider serviceProvider) + : FileBasedReporter(settings, serviceProvider) { internal const string SingleReportFileName = "VerificationResult.txt"; - public override async Task ReportAsync(IReadOnlyCollection errors) + public override async Task ReportAsync(VerificationResult verificationResult) { if (Settings.SplitIntoFiles) - await ReportByVerifier(errors); + await ReportByVerifier(verificationResult); else - await ReportWhole(errors); + await ReportWhole(verificationResult); } - private async Task ReportWhole(IReadOnlyCollection errors) + private async Task ReportWhole(VerificationResult result) { #if NET || NETSTANDARD2_1 await #endif using var streamWriter = new StreamWriter(CreateFile(SingleReportFileName)); - foreach (var error in errors.OrderBy(x => x.Id)) + await WriteHeader(result.Target, DateTime.Now, null, streamWriter); + + foreach (var error in result.Errors.OrderBy(x => x.Id)) await WriteError(error, streamWriter); } - private async Task ReportByVerifier(IReadOnlyCollection errors) + private async Task ReportByVerifier(VerificationResult result) { - var grouped = errors.GroupBy(x => x.VerifierChain.Last()); + var time = DateTime.Now; + var grouped = result.Errors.GroupBy(x => x.VerifierChain.Last().Name); foreach (var group in grouped) - await ReportToSingleFile(group); + await ReportToSingleFile(group, result.Target, time); } - private async Task ReportToSingleFile(IGrouping group) + private async Task ReportToSingleFile(IGrouping group, VerificationTarget target, DateTime time) { - var fileName = $"{GetVerifierName(group.Key)}Results.txt"; + var fileName = $"{GetVerifierName(group.Key)}_Results.txt"; #if NET || NETSTANDARD2_1 await #endif using var streamWriter = new StreamWriter(CreateFile(fileName)); + + await WriteHeader(target, time, group.Key, streamWriter); + foreach (var error in group.OrderBy(x => x.Id)) await WriteError(error, streamWriter); } @@ -52,23 +59,41 @@ private static string GetVerifierName(string verifierTypeName) { var typeNameSpan = verifierTypeName.AsSpan(); var nameIndex = typeNameSpan.LastIndexOf('.'); + var isSupType = typeNameSpan.IndexOf('/') > -1; - if (nameIndex == -1) + if (nameIndex == -1 && !isSupType) return verifierTypeName; - // They type name must not be empty - if (typeNameSpan.Length == nameIndex) - throw new InvalidOperationException(); - var name = typeNameSpan.Slice(nameIndex + 1); // Normalize subtypes (such as C/M) to avoid creating directories - if (name.IndexOf('/') != -1) + if (isSupType) return name.ToString().Replace('/', '.'); return name.ToString(); } + private static async Task WriteHeader( + VerificationTarget target, + DateTime time, + string? verifier, + StreamWriter writer) + { + var header = CreateHeader(target, time, verifier); + await writer.WriteLineAsync(header); + await writer.WriteLineAsync(); + } + + private static string CreateHeader(VerificationTarget target, DateTime time, string? verifier) + { + var sb = new StringBuilder(); + sb.Append($"# Target '{target.Name}'"); + if (!string.IsNullOrEmpty(verifier)) + sb.Append($"; Verifier: {verifier}"); + sb.Append($"; Time: {time:s}"); + return sb.ToString(); + } + private static async Task WriteError(VerificationError error, StreamWriter writer) { await writer.WriteLineAsync($"[{error.Id}] {error.Message}"); diff --git a/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs b/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs index 8fb833b..2d0e348 100644 --- a/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs +++ b/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs @@ -1,8 +1,6 @@ -using AET.ModVerify.Reporting.Settings; +namespace AET.ModVerify.Reporting.Reporters; -namespace AET.ModVerify.Reporting.Reporters.Text; - -public record TextFileReporterSettings : FileBasedReporterSettings +public sealed record TextFileReporterSettings : FileBasedReporterSettings { public bool SplitIntoFiles { get; init; } = true; } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/VerificationReportBroker.cs b/src/ModVerify/Reporting/Reporters/VerificationReportBroker.cs new file mode 100644 index 0000000..0ea6630 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/VerificationReportBroker.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace AET.ModVerify.Reporting.Reporters; + +public sealed class VerificationReportBroker : IVerificationReporter +{ + private readonly ILogger? _logger; + private readonly IReadOnlyCollection _reporters; + + public VerificationReportBroker( + IReadOnlyCollection reporters, + IServiceProvider serviceProvider) + { + _reporters = reporters ?? throw new ArgumentNullException(nameof(reporters)); + _logger = serviceProvider.GetService()?.CreateLogger(typeof(VerificationReportBroker)); + } + + public async Task ReportAsync(VerificationResult result) + { + foreach (var reporter in _reporters) + { + try + { + await reporter.ReportAsync(result); + } + catch (Exception e) + { + _logger?.LogError(e, "Exception while reporting verification error"); + } + } + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs deleted file mode 100644 index b8906ac..0000000 --- a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -using AET.ModVerify.Reporting.Reporters.JSON; -using AET.ModVerify.Reporting.Reporters.Text; -using AET.ModVerify.Reporting.Settings; -using Microsoft.Extensions.DependencyInjection; - -namespace AET.ModVerify.Reporting.Reporters; - -public static class VerificationReportersExtensions -{ - extension(IServiceCollection serviceCollection) - { - public IServiceCollection RegisterJsonReporter() - { - return serviceCollection.RegisterJsonReporter(new JsonReporterSettings - { - OutputDirectory = "." - }); - } - - public IServiceCollection RegisterTextFileReporter() - { - return serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings - { - OutputDirectory = "." - }); - } - - public IServiceCollection RegisterConsoleReporter(bool summaryOnly = false) - { - return serviceCollection.RegisterConsoleReporter(new ReporterSettings - { - MinimumReportSeverity = VerificationSeverity.Error - }, summaryOnly); - } - - public IServiceCollection RegisterJsonReporter(JsonReporterSettings settings) - { - return serviceCollection.AddSingleton(sp => new JsonReporter(settings, sp)); - } - - public IServiceCollection RegisterTextFileReporter(TextFileReporterSettings settings) - { - return serviceCollection.AddSingleton(sp => new TextFileReporter(settings, sp)); - } - - public IServiceCollection RegisterConsoleReporter(ReporterSettings settings, - bool summaryOnly = false) - { - return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, summaryOnly, sp)); - } - } -} \ No newline at end of file diff --git a/src/ModVerify/Reporting/RestoredVerifierInfo.cs b/src/ModVerify/Reporting/RestoredVerifierInfo.cs new file mode 100644 index 0000000..0c29488 --- /dev/null +++ b/src/ModVerify/Reporting/RestoredVerifierInfo.cs @@ -0,0 +1,10 @@ +using AET.ModVerify.Verifiers; + +namespace AET.ModVerify.Reporting; + +internal sealed class RestoredVerifierInfo : IGameVerifierInfo +{ + public IGameVerifierInfo? Parent { get; init; } + public required string Name { get; init; } + public string FriendlyName => Name; +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs deleted file mode 100644 index 5a51436..0000000 --- a/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AET.ModVerify.Reporting.Settings; - -//public record GlobalVerifyReportSettings -//{ -// //public VerificationSeverity MinimumReportSeverity { get; init; } = VerificationSeverity.Information; - -// public VerificationBaseline Baseline { get; init; } = VerificationBaseline.Empty; - -// public SuppressionList Suppressions { get; init; } = SuppressionList.Empty; -//} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs b/src/ModVerify/Reporting/Suppressions/Json/JsonSuppressionFilter.cs similarity index 91% rename from src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs rename to src/ModVerify/Reporting/Suppressions/Json/JsonSuppressionFilter.cs index 6d232c7..13f8335 100644 --- a/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs +++ b/src/ModVerify/Reporting/Suppressions/Json/JsonSuppressionFilter.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace AET.ModVerify.Reporting.Json; +namespace AET.ModVerify.Reporting.Suppressions.Json; internal class JsonSuppressionFilter(SuppressionFilter filter) { diff --git a/src/ModVerify/Reporting/Json/JsonSuppressionList.cs b/src/ModVerify/Reporting/Suppressions/Json/JsonSuppressionList.cs similarity index 88% rename from src/ModVerify/Reporting/Json/JsonSuppressionList.cs rename to src/ModVerify/Reporting/Suppressions/Json/JsonSuppressionList.cs index 1a8cb4a..54569eb 100644 --- a/src/ModVerify/Reporting/Json/JsonSuppressionList.cs +++ b/src/ModVerify/Reporting/Suppressions/Json/JsonSuppressionList.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Text.Json.Serialization; -namespace AET.ModVerify.Reporting.Json; +namespace AET.ModVerify.Reporting.Suppressions.Json; internal class JsonSuppressionList { diff --git a/src/ModVerify/Reporting/SuppressionFilter.cs b/src/ModVerify/Reporting/Suppressions/SuppressionFilter.cs similarity index 93% rename from src/ModVerify/Reporting/SuppressionFilter.cs rename to src/ModVerify/Reporting/Suppressions/SuppressionFilter.cs index efac588..35a21d7 100644 --- a/src/ModVerify/Reporting/SuppressionFilter.cs +++ b/src/ModVerify/Reporting/Suppressions/SuppressionFilter.cs @@ -1,8 +1,8 @@ using System; using System.Linq; -using AET.ModVerify.Reporting.Json; +using AET.ModVerify.Reporting.Suppressions.Json; -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Suppressions; public sealed class SuppressionFilter : IEquatable { @@ -42,7 +42,7 @@ public bool Suppresses(VerificationError error) if (Verifier is not null) { - if (error.VerifierChain.Contains(Verifier)) + if (error.VerifierChain.Any(x => x.Name.Equals(Verifier))) suppresses = true; else return false; diff --git a/src/ModVerify/Reporting/SuppressionList.cs b/src/ModVerify/Reporting/Suppressions/SuppressionList.cs similarity index 96% rename from src/ModVerify/Reporting/SuppressionList.cs rename to src/ModVerify/Reporting/Suppressions/SuppressionList.cs index 12ebab4..a5fc8f9 100644 --- a/src/ModVerify/Reporting/SuppressionList.cs +++ b/src/ModVerify/Reporting/Suppressions/SuppressionList.cs @@ -5,8 +5,9 @@ using System.IO; using System.Linq; using System.Text.Json; +using AET.ModVerify.Reporting.Suppressions.Json; -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Suppressions; public sealed class SuppressionList : IReadOnlyCollection { diff --git a/src/ModVerify/Reporting/VerificationCompletionStatus.cs b/src/ModVerify/Reporting/VerificationCompletionStatus.cs new file mode 100644 index 0000000..94fde18 --- /dev/null +++ b/src/ModVerify/Reporting/VerificationCompletionStatus.cs @@ -0,0 +1,8 @@ +namespace AET.ModVerify.Reporting; + +public enum VerificationCompletionStatus +{ + Completed, + CompletedFailFast, + Cancelled, +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerificationError.cs b/src/ModVerify/Reporting/VerificationError.cs index 538a8fd..d55f71b 100644 --- a/src/ModVerify/Reporting/VerificationError.cs +++ b/src/ModVerify/Reporting/VerificationError.cs @@ -17,7 +17,7 @@ public sealed class VerificationError : IEquatable public string Message { get; } - public IReadOnlyList VerifierChain { get; } + public IReadOnlyList VerifierChain { get; } public IReadOnlyCollection ContextEntries { get; } @@ -28,7 +28,7 @@ public sealed class VerificationError : IEquatable public VerificationError( string id, string message, - IReadOnlyList verifiers, + IReadOnlyList verifiers, IEnumerable contextEntries, string asset, VerificationSeverity severity) @@ -41,10 +41,9 @@ public VerificationError( Id = id; Message = message ?? throw new ArgumentNullException(nameof(message)); - VerifierChain = verifiers; + VerifierChain = [.. verifiers]; Severity = severity; - _contextEntries = [.. contextEntries]; - ContextEntries = _contextEntries.ToList(); + ContextEntries = _contextEntries = [.. contextEntries]; Asset = asset; } @@ -52,7 +51,7 @@ internal VerificationError(JsonVerificationError error) { Id = error.Id; Message = error.Message; - VerifierChain = error.VerifierChain; + VerifierChain = RestoreVerifierChain(error.VerifierChain); _contextEntries = [..error.ContextEntries]; ContextEntries = _contextEntries.ToList(); Asset = error.Asset; @@ -66,7 +65,7 @@ public static VerificationError Create( IEnumerable context, string asset) { - return new VerificationError(id, message, verifiers.Select(x => x.Name).ToList(), context, asset, severity); + return new VerificationError(id, message, verifiers, context, asset, severity); } public static VerificationError Create( @@ -79,7 +78,7 @@ public static VerificationError Create( return new VerificationError( id, message, - verifiers.Select(x => x.Name).ToList(), + verifiers, [], asset, severity); @@ -120,4 +119,23 @@ public override string ToString() return $"[{Severity}] [{string.Join(" --> ", VerifierChain)}] " + $"{Id}: Message={Message}; Asset='{Asset}'; Context=[{string.Join(",", ContextEntries)}];"; } + + private static IReadOnlyList RestoreVerifierChain(IReadOnlyList errorVerifierChain) + { + var verifierChain = new List(); + IGameVerifierInfo? previousVerifier = null; + + foreach (var name in errorVerifierChain) + { + var verifier = new RestoredVerifierInfo + { + Name = name, + Parent = previousVerifier + }; + verifierChain.Add(verifier); + previousVerifier = verifier; + } + + return verifierChain; + } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerificationReportBroker.cs b/src/ModVerify/Reporting/VerificationReportBroker.cs deleted file mode 100644 index 5c1e8a3..0000000 --- a/src/ModVerify/Reporting/VerificationReportBroker.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace AET.ModVerify.Reporting; - -public sealed class VerificationReportBroker(IServiceProvider serviceProvider) -{ - private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(VerificationReportBroker)); - - public async Task ReportAsync(IReadOnlyCollection errors) - { - var reporters = serviceProvider.GetServices(); - - foreach (var reporter in reporters) - { - try - { - await reporter.ReportAsync(errors); - } - catch (Exception e) - { - _logger?.LogError(e, "Exception while reporting verification error"); - } - } - } -} \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerificationResult.cs b/src/ModVerify/Reporting/VerificationResult.cs new file mode 100644 index 0000000..a4dbe1c --- /dev/null +++ b/src/ModVerify/Reporting/VerificationResult.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using AET.ModVerify.Reporting.Baseline; +using AET.ModVerify.Reporting.Suppressions; +using AET.ModVerify.Verifiers; + +namespace AET.ModVerify.Reporting; + +public sealed record VerificationResult +{ + public required VerificationCompletionStatus Status { get; init; } + + public required IReadOnlyCollection Errors + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + public required VerificationBaseline UsedBaseline + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + public required SuppressionList UsedSuppressions + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + public required IReadOnlyCollection Verifiers + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + public required VerificationTarget Target + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + public required TimeSpan Duration { get; init; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerifierChainEqualityComparer.cs b/src/ModVerify/Reporting/VerifierChainEqualityComparer.cs new file mode 100644 index 0000000..abb01ef --- /dev/null +++ b/src/ModVerify/Reporting/VerifierChainEqualityComparer.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using AET.ModVerify.Verifiers; + +namespace AET.ModVerify.Reporting; + +internal sealed class VerifierChainEqualityComparer : IEqualityComparer> +{ + public static readonly VerifierChainEqualityComparer Instance = new(); + + private VerifierChainEqualityComparer() + { + } + + public bool Equals(IReadOnlyList? x, IReadOnlyList? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + if (x.Count != y.Count) + return false; + for (var i = 0; i < x.Count; i++) + { + if (!NameBasedEqualityComparer.Instance.Equals(x[i], y[i])) + return false; + } + return true; + } + + public int GetHashCode(IReadOnlyList? obj) + { + if (obj == null) + return 0; + var hashCode = new HashCode(); + foreach (var verifier in obj) + hashCode.Add(verifier.Name); + return hashCode.ToHashCode(); + } +} diff --git a/src/ModVerify/Settings/VerifyPipelineSettings.cs b/src/ModVerify/Settings/VerifierServiceSettings.cs similarity index 72% rename from src/ModVerify/Settings/VerifyPipelineSettings.cs rename to src/ModVerify/Settings/VerifierServiceSettings.cs index 46fc997..762806d 100644 --- a/src/ModVerify/Settings/VerifyPipelineSettings.cs +++ b/src/ModVerify/Settings/VerifierServiceSettings.cs @@ -1,8 +1,6 @@ -using AET.ModVerify.Pipeline; +namespace AET.ModVerify.Settings; -namespace AET.ModVerify.Settings; - -public sealed class VerifyPipelineSettings +public sealed class VerifierServiceSettings { public required GameVerifySettings GameVerifySettings { get; init; } diff --git a/src/ModVerify/Utilities/VerificationErrorExtensions.cs b/src/ModVerify/Utilities/VerificationErrorExtensions.cs index e5aa9fe..f7eefbc 100644 --- a/src/ModVerify/Utilities/VerificationErrorExtensions.cs +++ b/src/ModVerify/Utilities/VerificationErrorExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Baseline; +using AET.ModVerify.Reporting.Suppressions; namespace AET.ModVerify.Utilities; diff --git a/src/ModVerify/VerificationTarget.cs b/src/ModVerify/VerificationTarget.cs index c0d5b97..f12bc4d 100644 --- a/src/ModVerify/VerificationTarget.cs +++ b/src/ModVerify/VerificationTarget.cs @@ -1,10 +1,9 @@ using System; -using System.Text; using PG.StarWarsGame.Engine; namespace AET.ModVerify; -public sealed class VerificationTarget +public sealed record VerificationTarget { public required GameEngineType Engine { get; init; } @@ -28,14 +27,4 @@ public required GameLocations Location public string? Version { get; init; } public bool IsGame => Location.ModPaths.Count == 0; - - public override string ToString() - { - var sb = new StringBuilder($"[Name={Name};EngineType={Engine};"); - if (!string.IsNullOrEmpty(Version)) - sb.Append($"Version={Version};"); - sb.Append($"Location={Location};"); - sb.Append(']'); - return sb.ToString(); - } } \ No newline at end of file diff --git a/src/ModVerify/Verifiers/GameEngineErrorCollector.cs b/src/ModVerify/Verifiers/GameEngineErrorCollector.cs index 6475ea7..975e360 100644 --- a/src/ModVerify/Verifiers/GameEngineErrorCollector.cs +++ b/src/ModVerify/Verifiers/GameEngineErrorCollector.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Threading; using AET.ModVerify.Reporting; -using AET.ModVerify.Reporting.Reporters.Engine; +using AET.ModVerify.Reporting.Engine; using AET.ModVerify.Settings; using PG.StarWarsGame.Engine; @@ -12,7 +12,8 @@ public sealed class GameEngineErrorCollector( IGameEngineErrorCollection errorCollection, IStarWarsGameEngine gameEngine, GameVerifySettings settings, - IServiceProvider serviceProvider) : GameVerifier(null, gameEngine, settings, serviceProvider) + IServiceProvider serviceProvider) + : GameVerifier(null, gameEngine, settings, serviceProvider) { public override string FriendlyName => "Game Engine Initialization"; diff --git a/src/ModVerify/Verifiers/GameVerifierBase.cs b/src/ModVerify/Verifiers/GameVerifierBase.cs index 02bacc4..22b2789 100644 --- a/src/ModVerify/Verifiers/GameVerifierBase.cs +++ b/src/ModVerify/Verifiers/GameVerifierBase.cs @@ -7,7 +7,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO.Abstractions; -using AET.ModVerify.Pipeline.Progress; +using AET.ModVerify.Progress; using PG.StarWarsGame.Engine; namespace AET.ModVerify.Verifiers; diff --git a/src/ModVerify/Verifiers/NameBasedEqualityComparer.cs b/src/ModVerify/Verifiers/NameBasedEqualityComparer.cs new file mode 100644 index 0000000..01f396f --- /dev/null +++ b/src/ModVerify/Verifiers/NameBasedEqualityComparer.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace AET.ModVerify.Verifiers; + +internal sealed class NameBasedEqualityComparer : IEqualityComparer, IEqualityComparer +{ + public static readonly NameBasedEqualityComparer Instance = new(); + + public bool Equals(GameVerifier? x, GameVerifier? y) + { + return Equals(x as IGameVerifierInfo, y); + } + + public int GetHashCode(GameVerifier? obj) + { + return GetHashCode(obj as IGameVerifierInfo); + } + + public bool Equals(IGameVerifierInfo? x, IGameVerifierInfo? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null) + return false; + if (y is null) + return false; + return x.Name == y.Name; + } + + public int GetHashCode(IGameVerifierInfo? obj) + { + return obj?.Name.GetHashCode() ?? 0; + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs index a36648b..4a5feb6 100644 --- a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs +++ b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs @@ -17,24 +17,37 @@ public sealed class ReferencedModelsVerifier( public override void Verify(CancellationToken token) { - var models = GameEngine.GameObjectTypeManager.Entries - .SelectMany(x => x.Models) - .Concat(FocHardcodedConstants.HardcodedModels).ToList(); + var gameObjectEntries = GameEngine.GameObjectTypeManager.Entries.ToList(); + var hardcodedModels = FocHardcodedConstants.HardcodedModels.ToList(); - if (models.Count == 0) + var totalModelsCount = gameObjectEntries.Sum(x => x.Models.Count()) + hardcodedModels.Count; + + if (totalModelsCount == 0) return; - var numModels = models.Count; var counter = 0; var inner = new SingleModelVerifier(this, GameEngine, Settings, Services); try { inner.Error += OnModelError; - foreach (var model in models) + + var context = new string[1]; + foreach (var gameObject in gameObjectEntries) + { + context[0] = $"GameObject: {gameObject.Name}"; + foreach (var model in gameObject.Models) + { + OnProgress((double)++counter / totalModelsCount, $"Model - '{model}'"); + inner.Verify(model, context, token); + } + } + + context[0] = "Hardcoded Model"; + foreach (var hardcodedModel in hardcodedModels) { - OnProgress((double)++counter / numModels, $"Model - '{model}'"); - inner.Verify(model, [], token); + OnProgress((double)++counter / totalModelsCount, $"Model - '{hardcodedModel}'"); + inner.Verify(hardcodedModel, context, token); } } finally @@ -47,4 +60,4 @@ private void OnModelError(object sender, VerificationErrorEventArgs e) { AddError(e.Error); } -} +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporter.cs deleted file mode 100644 index 0030344..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporter.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace PG.StarWarsGame.Engine.ErrorReporting; - -public abstract class GameEngineErrorReporter : IGameEngineErrorReporter -{ - public virtual void Report(XmlError error) - { - } - - public virtual void Report(InitializationError error) - { - } - - public virtual void Assert(EngineAssert assert) - { - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj index 326fd1d..47b3a7f 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj @@ -39,7 +39,4 @@ - - - \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj index 0d071ca..052190c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj @@ -24,7 +24,4 @@ - - - \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj index 5df0123..5328887 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj @@ -19,7 +19,4 @@ - - - \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj index be85ee8..5feadba 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj @@ -26,7 +26,4 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file diff --git a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj index ef3c9ef..58914c1 100644 --- a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj +++ b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj @@ -35,10 +35,6 @@ - - - - true true diff --git a/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs b/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs index ef7ad74..649aeb7 100644 --- a/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs +++ b/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using AET.ModVerify.Pipeline; +using AET.ModVerify; using AET.ModVerify.Settings; using AET.ModVerify.Verifiers; using PG.StarWarsGame.Engine;