public OrToolsSatGeneratedSyntaxTreeRegistryTests(ITestOutputHelper outputHelper) : base(outputHelper) { // ReSharper disable once RedundantEmptyObjectOrCollectionInitializer Registry = new OrToolsSatGeneratedSyntaxTreeRegistry { }; }
/// <summary> /// Loads the <paramref name="registry"/> and Purges Generated Code given /// <paramref name="serviceManager"/>. /// </summary> /// <param name="serviceManager"></param> /// <param name="registry"></param> private void LoadAndPurgeGeneratedCode(CodeGenerationServiceManager serviceManager , out OrToolsSatGeneratedSyntaxTreeRegistry registry) { if (DebugMessagesSwitch) { Writer.WriteLine("Loading and purging registry."); } /* Should not need to re-load any ServiceManager Registries, * since this is done inherently by the SM itself. */ registry = serviceManager.Registry; if (!registry.Any()) { if (DebugMessagesSwitch) { Writer.WriteLine("There are no Registry entries."); } return; } // Work around output or reference parameters. var local = registry; var outputDirectory = local.OutputDirectory; var anUpdateOccurred = local.GoogleOrToolsVersion < GoogleOrToolsVersion; // Purge All if any of them are missing. bool ShouldPurgeRegistry(GeneratedSyntaxTreeDescriptor descriptor) { /* Easy Purge to make by Version first, followed closely by whether * any previously registered files have themselves disappeared. */ bool WhetherSomeFilesAreMissing() { var someFilesAreMissing = descriptor.GeneratedAssetKeys .Select(y => Path.Combine(outputDirectory, y.RenderGeneratedFileName())) .Any(filePath => !File.Exists(filePath)); return(someFilesAreMissing); } var should = anUpdateOccurred || WhetherSomeFilesAreMissing(); if (DebugMessagesSwitch) { Writer.WriteLine($"Should{(should ? " " : " not ")}purge generated code."); } return(should); } registry.PurgeWhere(ShouldPurgeRegistry); }
/// <inheritdoc /> protected override JObject SerializeRegistry(OrToolsSatGeneratedSyntaxTreeRegistry registry) { var @object = base.SerializeRegistry(registry); const int fieldCount = 3; @object.Add( new JProperty(nameof(registry.GoogleOrToolsVersion) , registry.GoogleOrToolsVersion.ToString(fieldCount)) ); return(@object); }
private void VerifySingleCodeGenerationPass(out CodeGenerationConsoleManager manager , out OrToolsSatGeneratedSyntaxTreeRegistry registry) { // TODO: TBD: for a single pass, it might make sense to refactor this block of code... rinse and repeat for other scenarios... VerifyManager((m, a) => ConfigureDefaultArguments(a), DefaultErrorLevel, out manager, out registry); var local = registry; Path.Combine(registry.OutputDirectory, RegistryFileName).AssertFileExists(); void VerifyGeneratedFileExists(Guid key) => Path.Combine(local.OutputDirectory, key.RenderGeneratedFileName()).AssertFileExists(); registry.AssertNotNull().AssertNotEmpty().SelectMany(x => x.GeneratedAssetKeys) .ToList().ForEach(VerifyGeneratedFileExists); }
private static void VerifyDifferentRegistries(OrToolsSatGeneratedSyntaxTreeRegistry previousRegistry , OrToolsSatGeneratedSyntaxTreeRegistry currentRegistry) { { bool DirectoryExists(OrToolsSatGeneratedSyntaxTreeRegistry x) => Directory.Exists(x.OutputDirectory); // Do some quick verification of each Registry. previousRegistry = previousRegistry.AssertNotNull().AssertNotEmpty().AssertTrue(DirectoryExists); currentRegistry = currentRegistry.AssertNotNull().AssertNotEmpty().AssertTrue(DirectoryExists); } // Should always Not be the Same. previousRegistry.AssertNotSame(currentRegistry); var previousKeys = previousRegistry.SelectMany(x => x.GeneratedAssetKeys).AssertNotEmpty().ToList().AssertTrue(x => x.Any()); var currentKeys = currentRegistry.SelectMany(x => x.GeneratedAssetKeys).AssertNotEmpty().ToList().AssertTrue(x => x.Any()); { // None of the Second Registry Generated Keys should exist in the First Registry. void KeysDoesNotContain(IEnumerable <Guid> collection, Guid y) => collection.AssertDoesNotContain(x => x == y); // The two sets of Generated Code ought not to overlap whatsoever. currentKeys.ForEach(x => KeysDoesNotContain(previousKeys, x)); previousKeys.ForEach(x => KeysDoesNotContain(currentKeys, x)); } { // Newly Generated Code will exist, should also be able to verify this. void FileExists(string path) => path.AssertFileExists(); currentKeys.Select(x => Path.Combine(currentRegistry.OutputDirectory, x.RenderGeneratedFileName())).ToList().ForEach(FileExists); } { // Previously Generated Code will have been Purged, should be able to verify this. void FileDoesNotExist(string path) => path.AssertFileDoesNotExist(); previousKeys.Select(x => Path.Combine(previousRegistry.OutputDirectory, x.RenderGeneratedFileName())).ToList().ForEach(FileDoesNotExist); } }
internal void VerifyManager(ConfigureManagerCallback configure, int expectedErrorLevel , out CodeGenerationConsoleManager manager, out OrToolsSatGeneratedSyntaxTreeRegistry registry) { manager = Manager.AssertNotNull(); configure.AssertNotNull().Invoke(manager, Arguments); manager.TryParseOrShowHelp(Arguments.ToArray()).AssertTrue(); manager.Run(out var actualErrorLevel); // Whatever the outcome was, obtain that Registry. registry = manager.Registry; actualErrorLevel.AssertEqual(expectedErrorLevel); // ReSharper disable once SwitchStatementMissingSomeCases switch (expectedErrorLevel) { case DefaultErrorLevel: registry.AssertNotNull().AssertNotEmpty(); break; case ErrorGeneratingCode: // TODO: TBD: may elaborate on what this means, 'error' ... break; case MustSpecifyOutputDirectory: manager.OutputDirectoryVar.Value.AssertTrue(IsNullOrEmpty); registry.AssertNull(); break; case MustSpecifyRegistryFileName: manager.OutputDirectoryVar.Value.AssertNotNull().AssertNotEmpty(); manager.GeneratedCodeRegistryFileVar.Value.AssertTrue(IsNullOrEmpty); registry.AssertNull(); break; } }
// ReSharper disable once MemberCanBeMadeStatic.Local private int?GenerateCode() { void EnsureDirectoryExists(string path) { if (Directory.Exists(path)) { return; } Directory.CreateDirectory(path); } try { string outputDirectory = OutputDirectoryVar; string generatedCodeRegistryFile = GeneratedCodeRegistryFileVar; EnsureDirectoryExists(outputDirectory); if (DebugMessagesSwitch) { Writer.WriteLine($"Output Directory is '{outputDirectory}', registry file name is '{generatedCodeRegistryFile}'."); } var serviceManager = new CodeGenerationServiceManager(outputDirectory, generatedCodeRegistryFile); LoadAndPurgeGeneratedCode(serviceManager, out var registry); Registry = registry; registry.AssumesTrue(x => ReferenceEquals(x, serviceManager.Registry)); // ReSharper disable once RedundantEmptyObjectOrCollectionInitializer var service = new SatParameterCodeGeneratorService { }; var descriptor = service.Descriptor; service.CodeGenerationVisitor.Visit(descriptor); var compilationUnits = service.CodeGenerationVisitor.CompilationUnits; if (DebugMessagesSwitch) { Writer.WriteLine( $"There are {compilationUnits.Count} potential new compilation units as compared" + $" to {registry.SelectMany(x => x.GeneratedAssetKeys).Count()} old ones." ); } // TODO: TBD: this is what we are really talking about... // TODO: TBD: we need to find an exact match, otherwise, we reject the current set and regenerate... // TODO: TBD: no need to go ad-hoc compiling any previously generated code... that is entirely unnecessary. var renderedCompilationUnits = compilationUnits.ToDictionary( x => x.Key , x => x.Value.NormalizeWhitespace().ToFullString() ); if (TryEvaluateCompilationUnits(registry, renderedCompilationUnits)) { if (DebugMessagesSwitch) { Writer.WriteLine( "Evaluation complete, there are" + $" {renderedCompilationUnits.Count} rendered compilation units." ); } var generatedDescriptor = GeneratedSyntaxTreeDescriptor.Create(); foreach (var(key, renderedCompilationUnit) in renderedCompilationUnits) { var filePath = Path.Combine(outputDirectory, key.RenderGeneratedFileName()); using (var fs = File.Open(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read)) { using (var sw = new StreamWriter(fs)) { sw.WriteLine(renderedCompilationUnit); } } generatedDescriptor.GeneratedAssetKeys.Add(key); } registry.Add(generatedDescriptor); if (DebugMessagesSwitch) { Writer.WriteLine( "Service Manager Registry instance" + $" is{(ReferenceEquals(serviceManager.Registry, registry) ? " " : " not ")}the same." ); } if (DebugMessagesSwitch) { // TODO: TBD: do we need exposure of the SM Registry? Writer.WriteLine( $"There are {generatedDescriptor.GeneratedAssetKeys.Count} generated items and" + $" {serviceManager.Registry.SelectMany(x => x.GeneratedAssetKeys).Count()} total entries to save." ); } serviceManager.TrySave(); } } catch (Exception ex) { ReportException(ex); return(ErrorGeneratingCode); } return(null); }
/// <summary> /// We need to Evaluate a further and much more in depth Purge opportunity given the /// <paramref name="registry"/> and set of <paramref name="renderedCompilationUnits"/>. /// </summary> /// <param name="registry"></param> /// <param name="renderedCompilationUnits"></param> /// <returns></returns> private bool TryEvaluateCompilationUnits(OrToolsSatGeneratedSyntaxTreeRegistry registry , IRenderedCompilationUnitDictionary renderedCompilationUnits) { // Pretty sure we want to do an Ordinal comparison here. But this is a function of the Invocation. bool StringEquals(string a, string b, StringComparison comparisonType) => !(a == null || b == null) && string.Equals(a.Trim(), b.Trim(), comparisonType); bool TryDecomposeCompilationUnitSyntaxPair(CompilationUnitSyntaxPair pair , out string generatedCode) => !IsNullOrEmpty(generatedCode = pair.Value); // This is a bit involved, but this gains us sufficient accounting of Generated Code. bool ThereAreCodeGenerationInconsistencies(GeneratedSyntaxTreeDescriptor _) { var outputDirectory = registry.OutputDirectory; var filePaths = Directory.EnumerateFiles(outputDirectory, $"*{g}{cs}", TopDirectoryOnly); // Obtain any Previously Existing Baseline Generated Code in the form of a Stream Factory. var streamFactories = filePaths.Select(filePath => (Func <Stream>)( () => File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read) )); // Which has the added benefit of deferring Stream operations until we absolutely need them. foreach (var streamFactory in streamFactories) { string baselineGeneratedCode; /* And which also defers the actual delegate formation given the nature of the LINQ * provider. Performance may vary from OS to OS, from Run-time to Run-time. */ using (var fs = streamFactory.Invoke()) { // Do this operation Once and Only Once for the breadth of the candidate set of Generated Code. using (var sr = new StreamReader(fs)) { baselineGeneratedCode = sr.ReadToEndAsync().Result; } } // Continue when Code Generation Consistency checks can be verified. if (renderedCompilationUnits.Any( pair => TryDecomposeCompilationUnitSyntaxPair(pair, out var x) && StringEquals(x, baselineGeneratedCode, Ordinal))) { continue; } if (DebugMessagesSwitch) { Writer.WriteLine("Generated code inconsistencies discovered, purge required."); } // Otherwise, break, we know the set to be Inconsistent, i.e. re-Generation required. return(true); } if (DebugMessagesSwitch) { Writer.WriteLine("Generated code found to be consistent, purge not required."); } // If everything checked out, All Consistent, then we are Clear to Bypass Code Generation. return(false); } var areNonePreviouslyGenerated = !registry.SelectMany(x => x.GeneratedAssetKeys).Any(); var areObviouslyDifferent = registry.SelectMany(x => x.GeneratedAssetKeys).Count() != renderedCompilationUnits.Count; var thereAreCodeGenerationInconsistencies = registry.Any(ThereAreCodeGenerationInconsistencies); var shouldPurge = areNonePreviouslyGenerated || areObviouslyDifferent || thereAreCodeGenerationInconsistencies; registry.PurgeWhere(_ => shouldPurge); return(shouldPurge); }
private void VerifySingleCodeGenerationPass(out OrToolsSatGeneratedSyntaxTreeRegistry registry) => VerifySingleCodeGenerationPass(out _, out registry);