From f1cee6533e3b5eddcdb4591429ecd8af5b0378ce Mon Sep 17 00:00:00 2001 From: JerrettDavis Date: Mon, 1 Jun 2026 09:33:10 -0500 Subject: [PATCH] test: improve generator coverage --- .../CompensatingTransactionGeneratorTests.cs | 80 +++++++++++++++++++ .../DecoratorGeneratorTests.cs | 54 +++++++++++++ 2 files changed, 134 insertions(+) diff --git a/test/PatternKit.Generators.Tests/CompensatingTransactionGeneratorTests.cs b/test/PatternKit.Generators.Tests/CompensatingTransactionGeneratorTests.cs index a5ca0c79..63b17eec 100644 --- a/test/PatternKit.Generators.Tests/CompensatingTransactionGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/CompensatingTransactionGeneratorTests.cs @@ -68,6 +68,7 @@ public sealed class Ctx; [Scenario("Reports diagnostics for invalid compensating transaction steps")] [Theory] + [InlineData("[CompensatingTransactionStep(\"x\", 1, Compensation = nameof(Undo))] private static ValueTask Do(MissingContext c, CancellationToken ct) => default; private static ValueTask Undo(MissingContext c, CancellationToken ct) => default;")] [InlineData("[CompensatingTransactionStep(\"x\", 1, Compensation = nameof(Undo))] private static Task Do(Ctx c, CancellationToken ct) => Task.CompletedTask; private static ValueTask Undo(Ctx c, CancellationToken ct) => default;")] [InlineData("[CompensatingTransactionStep(\"x\", 1, Compensation = nameof(Undo))] private ValueTask Do(Ctx c, CancellationToken ct) => default; private static ValueTask Undo(Ctx c, CancellationToken ct) => default;")] [InlineData("[CompensatingTransactionStep(\"x\", 1)] private static ValueTask Do(Ctx c, CancellationToken ct) => default;")] @@ -109,6 +110,85 @@ public static partial class Host ScenarioExpect.Contains(result.Diagnostics, diagnostic => diagnostic.Id == "PKCOMP004")) .AssertPassed(); + [Scenario("Generates compensating transaction host type shapes")] + [Fact] + public Task Generates_Compensating_Transaction_Host_Type_Shapes() + => Given("compensating transaction hosts with different accessibility and type shapes", () => Compile(""" + using System.Threading; + using System.Threading.Tasks; + using PatternKit.Generators.CompensatingTransactions; + namespace Demo; + public sealed class Ctx; + + [GenerateCompensatingTransaction(TransactionName = "shape-\"one")] + internal abstract partial class AbstractHost + { + [CompensatingTransactionStep("abstract\\\"step", 1, Compensation = nameof(Undo))] + private static ValueTask Do(Ctx c, CancellationToken ct) => default; + private static ValueTask Undo(Ctx c, CancellationToken ct) => default; + } + + [GenerateCompensatingTransaction] + public sealed partial class SealedHost + { + [CompensatingTransactionStep("sealed", 1, Compensation = nameof(Undo))] + private static ValueTask Do(Ctx c, CancellationToken ct) => default; + private static ValueTask Undo(Ctx c, CancellationToken ct) => default; + } + + [GenerateCompensatingTransaction] + internal partial struct StructHost + { + [CompensatingTransactionStep("struct", 1, Compensation = nameof(Undo))] + private static ValueTask Do(Ctx c, CancellationToken ct) => default; + private static ValueTask Undo(Ctx c, CancellationToken ct) => default; + } + + public partial class Container + { + [GenerateCompensatingTransaction] + private partial class PrivateHost + { + [CompensatingTransactionStep("private", 1, Compensation = nameof(Undo))] + private static ValueTask Do(Ctx c, CancellationToken ct) => default; + private static ValueTask Undo(Ctx c, CancellationToken ct) => default; + } + + [GenerateCompensatingTransaction] + private protected partial class PrivateProtectedHost + { + [CompensatingTransactionStep("private-protected", 1, Compensation = nameof(Undo))] + private static ValueTask Do(Ctx c, CancellationToken ct) => default; + private static ValueTask Undo(Ctx c, CancellationToken ct) => default; + } + + [GenerateCompensatingTransaction] + protected internal partial class ProtectedInternalHost + { + [CompensatingTransactionStep("protected-internal", 1, Compensation = nameof(Undo))] + private static ValueTask Do(Ctx c, CancellationToken ct) => default; + private static ValueTask Undo(Ctx c, CancellationToken ct) => default; + } + } + """)) + .Then("the generated sources preserve shape and escaping", result => + { + ScenarioExpect.Empty(result.Diagnostics); + ScenarioExpect.Equal(6, result.GeneratedSources.Count); + + var combined = string.Join("\n", result.GeneratedSources); + ScenarioExpect.Contains("internal abstract partial class AbstractHost", combined); + ScenarioExpect.Contains("CompensatingTransaction.Create(\"shape-\\\"one\")", combined); + ScenarioExpect.Contains(".AddStep(\"abstract\\\\\\\"step\"", combined); + ScenarioExpect.Contains("public sealed partial class SealedHost", combined); + ScenarioExpect.Contains("internal partial struct StructHost", combined); + ScenarioExpect.Contains("private partial class PrivateHost", combined); + ScenarioExpect.Contains("private protected partial class PrivateProtectedHost", combined); + ScenarioExpect.Contains("protected internal partial class ProtectedInternalHost", combined); + ScenarioExpect.True(result.EmitSuccess, string.Join(Environment.NewLine, result.EmitDiagnostics)); + }) + .AssertPassed(); + [Scenario("Generates nested record host wrappers")] [Fact] public Task Generates_Nested_Record_Host_Wrappers() diff --git a/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs b/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs index 236eb622..b7279664 100644 --- a/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs @@ -1117,6 +1117,7 @@ namespace TestNamespace; public abstract class RepositoryBase { public abstract string Name { get; internal set; } + public abstract string Secret { protected internal get; set; } public abstract int Version { set; } public abstract void Copy(ref int source, out int destination, in bool enabled); public abstract ref int GetCurrent(); @@ -1144,6 +1145,7 @@ public abstract class RepositoryBase ScenarioExpect.Contains("public abstract partial class RepositoryBaseDecoratorBase", generatedSource); ScenarioExpect.Contains("override string Name", generatedSource); ScenarioExpect.Contains("internal set => Inner.Name = value;", generatedSource); + ScenarioExpect.Contains("protected internal get => Inner.Secret;", generatedSource); ScenarioExpect.Contains("override int Version", generatedSource); ScenarioExpect.Contains("set => Inner.Version = value;", generatedSource); ScenarioExpect.Contains("public override void Copy(ref int source, out int destination, in bool enabled)", generatedSource); @@ -1194,6 +1196,42 @@ public class NestedType ScenarioExpect.Contains(diagnostics, d => d.Id == "PKDEC002" && d.GetMessage().Contains("NestedType")); } + [Scenario("Diagnostic PKDEC004 InaccessibleProtectedPropertyDeclaration")] + [Fact] + public void Diagnostic_PKDEC004_InaccessibleProtectedPropertyDeclaration() + { + const string source = """ + using PatternKit.Generators.Decorator; + + namespace TestNamespace; + + [GenerateDecorator] + public abstract class SecretRepository + { + protected abstract string Secret { get; } + public abstract string Visible { get; } + } + """; + + var comp = RoslynTestHelpers.CreateCompilation(source, nameof(Diagnostic_PKDEC004_InaccessibleProtectedPropertyDeclaration)); + var gen = new DecoratorGenerator(); + _ = RoslynTestHelpers.Run(comp, gen, out var result, out var updated); + + ScenarioExpect.Contains(result.Results.SelectMany(r => r.Diagnostics), d => d.Id == "PKDEC004" && d.GetMessage().Contains("Secret")); + + var generatedSource = result.Results + .SelectMany(r => r.GeneratedSources) + .Single(gs => gs.HintName == "TestNamespace_SecretRepository.Decorator.g.cs") + .SourceText.ToString(); + + ScenarioExpect.Contains("override string Visible => Inner.Visible;", generatedSource); + ScenarioExpect.DoesNotContain("override string Secret", generatedSource); + ScenarioExpect.DoesNotContain("=> Inner.Secret", generatedSource); + + var emit = updated.Emit(Stream.Null); + ScenarioExpect.True(emit.Success, string.Join("\n", emit.Diagnostics)); + } + [Scenario("GenerateDecorator PreservesPrimitiveAndStringDefaultLiterals")] [Fact] public void GenerateDecorator_PreservesPrimitiveAndStringDefaultLiterals() @@ -1210,10 +1248,18 @@ public interface ILiteralService { string Format( string text = "line\n\"quoted\"\tend", + string? optionalText = null, + object boxed = null, + int? retryCount = null, bool enabled = true, + float missing = float.NaN, + float negativeWeight = float.NegativeInfinity, float ratio = 1.25f, + double overflow = double.PositiveInfinity, + double underflow = double.NegativeInfinity, double weight = 2.5d, decimal amount = 3.75m, + Mode known = Mode.Known, Mode mode = (Mode)99); } """; @@ -1230,10 +1276,18 @@ string Format( .SourceText.ToString(); ScenarioExpect.Contains("string text = \"line\\n\\\"quoted\\\"\\tend\"", generatedSource); + ScenarioExpect.Contains("string? optionalText = null", generatedSource); + ScenarioExpect.Contains("object boxed = null", generatedSource); + ScenarioExpect.Contains("int? retryCount = null", generatedSource); ScenarioExpect.Contains("bool enabled = true", generatedSource); + ScenarioExpect.Contains("float missing = float.NaN", generatedSource); + ScenarioExpect.Contains("float negativeWeight = float.NegativeInfinity", generatedSource); ScenarioExpect.Contains("float ratio = 1.25f", generatedSource); + ScenarioExpect.Contains("double overflow = double.PositiveInfinity", generatedSource); + ScenarioExpect.Contains("double underflow = double.NegativeInfinity", generatedSource); ScenarioExpect.Contains("double weight = 2.5d", generatedSource); ScenarioExpect.Contains("decimal amount = 3.75m", generatedSource); + ScenarioExpect.Contains("global::TestNamespace.Mode known = global::TestNamespace.Mode.Known", generatedSource); ScenarioExpect.Contains("global::TestNamespace.Mode mode = (global::TestNamespace.Mode)99", generatedSource); var emit = updated.Emit(Stream.Null);