Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;")]
Expand Down Expand Up @@ -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<global::Demo.Ctx>.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()
Expand Down
54 changes: 54 additions & 0 deletions test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()
Expand All @@ -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);
}
""";
Expand All @@ -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);
Expand Down
Loading