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
18 changes: 15 additions & 3 deletions src/Actors.Analyzer/CodeFixes/FixCaepActr702.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CodeArchitects.Platform.Actors.Analyzer.Utils;
using CodeArchitects.Platform.Actors.Analyzer.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -63,6 +63,7 @@ private static Document ImplementActorMessageInterface(MessageTypeInfo info, INa
bool isIdTypeAccessible = idTypeSymbol.IsAccessible(semanticModel, @class.SpanStart);

GenericNameSyntax newBaseTypeName = CreateNewBaseTypeName(idTypeSymbol);
SyntaxTrivia endOfLine = GetEndOfLine(root);

root = baseType is null
? AddActorMessageInterface(root, info.Class, newBaseTypeName)
Expand All @@ -87,7 +88,7 @@ private static Document ImplementActorMessageInterface(MessageTypeInfo info, INa
semicolonToken: SyntaxFactory.Token(
leading: default,
kind: SyntaxKind.SemicolonToken,
trailing: SyntaxFactory.TriviaList(SyntaxFactory.LineFeed))));
trailing: SyntaxFactory.TriviaList(endOfLine))));
}

if (!isIdTypeAccessible)
Expand Down Expand Up @@ -119,12 +120,23 @@ private static Document ImplementActorMessageInterface(MessageTypeInfo info, INa
semicolonToken: SyntaxFactory.Token(
leading: default,
kind: SyntaxKind.SemicolonToken,
trailing: SyntaxFactory.TriviaList(SyntaxFactory.LineFeed))));
trailing: SyntaxFactory.TriviaList(endOfLine))));
}

return document.WithSyntaxRoot(root);
}

private static SyntaxTrivia GetEndOfLine(CompilationUnitSyntax root)
{
foreach (SyntaxTrivia trivia in root.DescendantTrivia(descendIntoTrivia: true))
{
if (trivia.IsKind(SyntaxKind.EndOfLineTrivia))
return trivia;
}

return SyntaxFactory.CarriageReturnLineFeed;
}

private static CompilationUnitSyntax ChangeActorMessageInterface(CompilationUnitSyntax root, BaseTypeSyntax baseType, GenericNameSyntax newBaseTypeName)
{
return root.ReplaceNode(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;

Expand All @@ -12,6 +14,21 @@ internal class ActorCodeFixTest : CSharpCodeFixTest<ActorDiagnosticAnalyzer, Act
public ActorCodeFixTest()
{
ActorTest.SetupTestState(TestState);

const string editorConfig = """
root = true

[*]
end_of_line = lf
""";

TestState.AnalyzerConfigFiles.Add(("/.editorconfig", editorConfig));
FixedState.AnalyzerConfigFiles.Add(("/.editorconfig", editorConfig));

SolutionTransforms.Add((solution, _) => NormalizeSolutionLineEndings(solution));

SolutionTransforms.Add((solution, projectId) =>
solution.WithOptions(solution.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n")));
}

public void ModifyCompilationOptions(Func<CompilationOptions, CompilationOptions> optionsTransform)
Expand All @@ -33,4 +50,27 @@ protected override async Task<Compilation> GetProjectCompilationAsync(Project pr

return ActorTest.GetProjectCompilation(originalCompilation, cancellationToken);
}

private static Solution NormalizeSolutionLineEndings(Solution solution)
{
foreach (DocumentId documentId in solution.Projects.SelectMany(project => project.DocumentIds))
{
Document? document = solution.GetDocument(documentId);
if (document is null)
{
continue;
}

SourceText? sourceText = document.GetTextAsync().GetAwaiter().GetResult();
string normalized = sourceText.ToString().Replace("\r\n", "\n").Replace("\r", "\n");
if (normalized == sourceText.ToString())
{
continue;
}

solution = solution.WithDocumentText(documentId, SourceText.From(normalized, sourceText.Encoding));
}

return solution;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;

namespace CodeArchitects.Platform.Actors.Analyzer.Fixtures;
Expand Down Expand Up @@ -48,19 +48,27 @@ public ActorCodeFixVerifier ModifyCompilationOptions(Func<CompilationOptions, Co

private static void AddSource(SolutionState state, string source, DiagnosticResult[] diagnostics, string? fileName = null)
{
string normalizedSource = NormalizeLineEndings(source);

if (fileName is not null)
{
state.Sources.Add((fileName, source));
state.Sources.Add((fileName, normalizedSource));
}
else
{
state.Sources.Add(source);
state.Sources.Add(normalizedSource);
}

string path = state.Sources[^1].filename;
state.ExpectedDiagnostics.AddRange(diagnostics.Select(diagnostic => diagnostic.WithDefaultPath(path)));
}

private static string NormalizeLineEndings(string source)
{
// Keep a stable newline convention for verifier snapshots across TFMs and OSes.
return source.Replace("\r\n", "\n").Replace("\r", "\n");
}

public static Task Verify(Action<ActorCodeFixVerifier> specification, CancellationToken cancellationToken = default)
{
ActorCodeFixTest test = new();
Expand Down
64 changes: 62 additions & 2 deletions tests/Actors.Dapr.AspNetCore.IntegrationTests/TestFixture.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using CliWrap;
using CliWrap;
using CliWrap.Exceptions;
using System.Net;
using System.Text;

namespace CodeArchitects.Platform.Actors.Dapr.AspNetCore;

public class TestFixture : IAsyncLifetime
{
private static readonly string s_dockerComposePath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "../../.."));
private static readonly Uri s_daprHealthUri = new("http://localhost:3500/v1.0/healthz");
private static readonly Uri s_appHealthUri = new("http://localhost:20100/virtual-actor/binding-disabler");
private readonly IReadOnlyDictionary<string, string?> _envVars;
private const string LATEST_FRAMEWORK = "10.0";

Expand Down Expand Up @@ -62,7 +65,7 @@ await Cli.Wrap("docker-compose")
throw new Exception($"Exception in executing the command. Error log:\n{errorSb}", ex);
}

await Task.Delay(5000);
await WaitForServiceReadinessAsync();
}

public async Task DisposeAsync()
Expand All @@ -73,4 +76,61 @@ await Cli.Wrap("docker")
.WithEnvironmentVariables(_envVars)
.ExecuteAsync();
}

private async Task WaitForServiceReadinessAsync()
{
using HttpClient http = new();
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(90));

Exception? daprLastError = null;
Exception? appLastError = null;

while (!cts.Token.IsCancellationRequested)
{
try
{
using HttpResponseMessage response = await http.GetAsync(s_daprHealthUri, cts.Token);
if (response.IsSuccessStatusCode)
{
daprLastError = null;
break;
}

daprLastError = new InvalidOperationException($"Unexpected Dapr health status code {(int)response.StatusCode}.");
}
catch (Exception ex)
{
daprLastError = ex;
}

await Task.Delay(1000);
}

if (daprLastError is not null)
{
throw new TimeoutException($"Dapr sidecar did not become ready at {s_daprHealthUri}.", daprLastError);
}

while (!cts.Token.IsCancellationRequested)
{
try
{
using HttpResponseMessage response = await http.GetAsync(s_appHealthUri, cts.Token);
if (response.StatusCode == HttpStatusCode.OK)
{
return;
}

appLastError = new InvalidOperationException($"Unexpected app readiness status code {(int)response.StatusCode}.");
}
catch (Exception ex)
{
appLastError = ex;
}

await Task.Delay(1000);
}

throw new TimeoutException($"Actor app did not become ready at {s_appHealthUri}.", appLastError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ services:
- HTTP_PORT=${HTTP_PORT}
ports:
- 20100:${HTTP_PORT}
- 3500:3500
- 50001:50001
environment:
- ASPNETCORE_ENVIRONMENT=Development
depends_on:
Expand Down Expand Up @@ -48,7 +50,7 @@ services:
- 5672:5672
- 15672:15672
healthcheck:
test: rabbitmq-diagnostics -q ping
test: ["CMD-SHELL", "rabbitmq-diagnostics -q check_running && rabbitmq-diagnostics -q check_port_connectivity"]
interval: 15s
timeout: 5s
retries: 5
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CodeArchitects.Platform.Data.AdoNet.Fixtures;
using CodeArchitects.Platform.Data.AdoNet.Fixtures;
using CodeArchitects.Platform.Data.AdoNet.Fixtures.Models;
using CodeArchitects.Platform.Data.AdoNet.Model;
using CodeArchitects.Platform.Data.AdoNet.MySQL.Command;
Expand Down Expand Up @@ -36,10 +36,15 @@ public abstract class SelectTextDataAttribute : DataAttribute
return queryCompiler(root, sqlBuilder);
});

yield return new object[] { new SqlTextBuilder(cacheMock.Object, new SQLServerSyntaxProvider()), Root, ExpectedTextSQLServer };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new PostgreSQLSyntaxProvider()), Root, ExpectedTextPostgreSQL };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new OracleSyntaxProvider()), Root, ExpectedTextOracle };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new MySQLSyntaxProvider()), Root, ExpectedTextMySQL };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new SQLServerSyntaxProvider()), Root, NormalizeLineEndings(ExpectedTextSQLServer) };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new PostgreSQLSyntaxProvider()), Root, NormalizeLineEndings(ExpectedTextPostgreSQL) };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new OracleSyntaxProvider()), Root, NormalizeLineEndings(ExpectedTextOracle) };
yield return new object[] { new SqlTextBuilder(cacheMock.Object, new MySQLSyntaxProvider()), Root, NormalizeLineEndings(ExpectedTextMySQL) };
}

private static string NormalizeLineEndings(string text)
{
return text.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", Environment.NewLine);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using CliWrap;
using CliWrap.Exceptions;
using System.Net;
using System.Text;

namespace CodeArchitects.Platform.Messaging.Dapr.AspNetCore;

public class TestFixture : IAsyncLifetime
{
private static readonly string s_dockerComposePath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "../../.."));
private static readonly Uri s_subscriberWaitBaseUri = new("http://localhost:20001/wait/");
private static readonly Uri s_publisherSendBaseUri = new("http://localhost:20000/noresult/by-reflection/send/");
private readonly IReadOnlyDictionary<string, string?> _envVars;
private const string LATEST_FRAMEWORK = "10.0";

Expand Down Expand Up @@ -62,7 +65,7 @@ await Cli.Wrap("docker-compose")
throw new Exception($"Exception in executing the command. Error log:\n{errorSb}", ex);
}

await Task.Delay(15000);
await WaitForServiceReadinessAsync();
}

public async Task DisposeAsync()
Expand All @@ -73,4 +76,45 @@ await Cli.Wrap("docker")
.WithEnvironmentVariables(_envVars)
.ExecuteAsync();
}

private static async Task WaitForServiceReadinessAsync()
{
using HttpClient http = new();
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(120));

Exception? lastError = null;

while (!cts.Token.IsCancellationRequested)
{
Guid probeId = Guid.NewGuid();

try
{
// Subscriber readiness (process up and endpoint mapped)
using HttpResponseMessage waitProbe = await http.GetAsync(new Uri(s_subscriberWaitBaseUri, $"{probeId}?millisecondsTimeout=1"), cts.Token);
if (waitProbe.StatusCode != HttpStatusCode.OK && waitProbe.StatusCode != HttpStatusCode.NoContent)
{
throw new InvalidOperationException($"Unexpected subscriber status code {(int)waitProbe.StatusCode}.");
}

// Publisher + sidecar + bus readiness (fails with 500 while Dapr/pubsub not ready)
using HttpResponseMessage sendProbe = await http.GetAsync(new Uri(s_publisherSendBaseUri, probeId.ToString()), cts.Token);
if (sendProbe.StatusCode == HttpStatusCode.OK)
{
return;
}

string body = await sendProbe.Content.ReadAsStringAsync(cts.Token);
throw new InvalidOperationException($"Unexpected publisher status code {(int)sendProbe.StatusCode}. Body: {body}");
}
catch (Exception ex)
{
lastError = ex;
}

await Task.Delay(1000);
}

throw new TimeoutException("Messaging integration services did not become ready in time.", lastError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ services:
- 5672:5672
- 15672:15672
healthcheck:
test: rabbitmq-diagnostics -q ping
test: ["CMD-SHELL", "rabbitmq-diagnostics -q check_running && rabbitmq-diagnostics -q check_port_connectivity"]
interval: 20s
timeout: 5s
retries: 5
Loading