Skip to content
Open
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
37 changes: 37 additions & 0 deletions .github/workflows/code-style.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Code style

on:
workflow_call:
workflow_dispatch:

jobs:
verify:
name: Verify code style
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
env:
DOTNET_CLI_UI_LANGUAGE: en-US
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Restore solution
run: dotnet restore Eventuous.slnx
- name: Verify code style
shell: bash
run: |
set -euo pipefail
log_file="$RUNNER_TEMP/dotnet-format.log"
dotnet format style Eventuous.slnx --verify-no-changes --no-restore --severity warn --verbosity diagnostic 2>&1 | tee "$log_file"
if grep -Fq 'Required references did not load for ' "$log_file"; then
echo "::error::Code style verification was incomplete because project references could not be loaded."
exit 1
fi
6 changes: 5 additions & 1 deletion .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
- '*.md'

jobs:
code-style:
name: Code style
uses: ./.github/workflows/code-style.yml

event_file:
# Consumed by the Test Results workflow (test-results.yml) to publish results on the commit.
name: "Event File"
Expand All @@ -28,7 +32,7 @@ jobs:

nuget:
runs-on: ubuntu-latest
needs: tests
needs: [ tests, code-style ]
permissions:
id-token: write
contents: read
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ concurrency:
cancel-in-progress: true

jobs:
code-style:
name: Code style
uses: ./.github/workflows/code-style.yml

event_file:
# Consumed by the Test Results workflow (test-results.yml) to publish results with the
# correct PR context.
Expand Down
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,22 @@ Here are simple rules that we expect to be followed by contributors:
- Use the code formats of the repository, we have them in the `.editorconfig` file. Reformat the code accordingly if you copy it from somewhere.
- Only contribute with your own work, or if you have a permission from the others to contribute their code.

## Code style checks

The reusable [code-style workflow](.github/workflows/code-style.yml) runs for pull requests and pushes to `dev`.

Run the same style verification locally from the repository root:

```sh
dotnet format style Eventuous.slnx --verify-no-changes --severity warn
```

This checks Roslyn code-style rules at warning or error severity, including naming conventions and unnecessary imports. Apply available fixes with:

```sh
dotnet format style Eventuous.slnx --severity warn
```

Some violations, such as naming-rule violations, require manual fixes. Rerun verification after making changes. CI also fails if the formatter reports that required project references could not be loaded.

Thanks for contributing!
8 changes: 4 additions & 4 deletions src/Core/src/Eventuous.Diagnostics/EventuousDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ public static class EventuousDiagnostics {
static EventuousDiagnostics() {
Enabled = Environment.GetEnvironmentVariable("EVENTUOUS_DISABLE_DIAGS") != "1";
ActivitySource = new(InstrumentationName, InstrumentationVersion.Value);
listener = DummyActivityListener.Create();
ActivitySource.AddActivityListener(listener);
Listener = DummyActivityListener.Create();
ActivitySource.AddActivityListener(Listener);
}

public const string InstrumentationName = DiagnosticName.BaseName;

static readonly ActivityListener listener;
static readonly ActivityListener Listener;

public static KeyValuePair<string, object?>[] Tags { get; private set; } = [];

Expand Down Expand Up @@ -46,7 +46,7 @@ public static void AddDefaultTag(string key, object? value) {

public static ActivitySource ActivitySource { get; }

public static void RemoveDummyListener() => listener.Dispose();
public static void RemoveDummyListener() => Listener.Dispose();

public static Meter GetMeter(string name) => new(name, InstrumentationVersion.Value);

Expand Down
6 changes: 3 additions & 3 deletions src/Core/src/Eventuous.Persistence/ExpectedStreamVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ public readonly record struct ExpectedStreamVersion(long Value) {
}

public record struct StreamReadPosition {
public StreamReadPosition(long Value) {
if (Value < 0) throw new ArgumentOutOfRangeException(nameof(Value), "StreamReadPosition cannot be negative.");
public StreamReadPosition(long value) {
if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "StreamReadPosition cannot be negative.");
Comment thread
nmummau marked this conversation as resolved.

this.Value = Value;
Value = value;
}

public static readonly StreamReadPosition Start = new(0L);
Expand Down
8 changes: 4 additions & 4 deletions src/Core/src/Eventuous.Serialization/EventSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ namespace Eventuous;
/// </summary>
[PublicAPI]
public static class EventSerializer {
static IEventSerializer? _default;
static IEventSerializer? defaultSerializer;

/// <summary>
/// Gets the default event serializer. Throws if not configured.
/// </summary>
public static IEventSerializer Default => _default
public static IEventSerializer Default => defaultSerializer
?? throw new InvalidOperationException(
"No default event serializer configured. "
+ "Call EventSerializer.SetDefault() at startup or register IEventSerializer in DI. "
Expand All @@ -25,7 +25,7 @@ public static class EventSerializer {
/// Sets the default event serializer instance.
/// </summary>
public static void SetDefault(IEventSerializer serializer)
=> _default = serializer ?? throw new ArgumentNullException(nameof(serializer));
=> defaultSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));

/// <summary>
/// Sets the default event serializer only if one has not already been configured.
Expand All @@ -34,6 +34,6 @@ public static void SetDefault(IEventSerializer serializer)
public static bool TrySetDefault(IEventSerializer serializer) {
ArgumentNullException.ThrowIfNull(serializer);

return Interlocked.CompareExchange(ref _default, serializer, null) == null;
return Interlocked.CompareExchange(ref defaultSerializer, serializer, null) == null;
}
}
7 changes: 3 additions & 4 deletions src/Core/src/Eventuous.Shared/TypeMap/TypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,22 @@ struct MapEntry {
public TV? Value { get; set; }
}

// ReSharper disable once InconsistentNaming
// ReSharper disable once StaticMemberInGenericType
static volatile int _index = -1;
static volatile int index = -1;

const int DefaultCapacity = 10;

// ReSharper disable once UnusedTypeParameter
static class TypeSlot<T> {
// ReSharper disable once StaticMemberInGenericType
internal static readonly int Index = Interlocked.Increment(ref _index);
internal static readonly int Index = Interlocked.Increment(ref index);
}

static int GetIndex<TK>() => TypeSlot<TK>.Index;

static int RecommendedCapacity {
get {
var capacity = _index + 1;
var capacity = index + 1;

if (capacity < DefaultCapacity) {
capacity = DefaultCapacity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public async Task Should_stay_silent_when_event_type_attribute_cannot_be_resolve
var compilation = CreateCompilation(source, includeSharedAssembly: false);
var analyzer = new EventUsageAnalyzer();

var diagnostics = await GetAnalyzerDiagnosticsAsync(compilation, analyzer);
var diagnostics = await GetAnalyzerDiagnosticsAsync(compilation, analyzer, allowCompilationErrors: true);

await Assert.That(diagnostics.Length).IsEqualTo(0);
}
Expand All @@ -128,11 +128,18 @@ public async Task Should_resolve_all_well_known_type_names() {
await Assert.That(unresolved).IsEmpty();
}

static async Task<Diagnostic[]> GetAnalyzerDiagnosticsAsync(Compilation compilation, EventUsageAnalyzer analyzer) {
static async Task<Diagnostic[]> GetAnalyzerDiagnosticsAsync(Compilation compilation, EventUsageAnalyzer analyzer, bool allowCompilationErrors = false) {
if (!allowCompilationErrors) {
var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray();
await Assert.That(errors).IsEmpty();
}

var withAnalyzers = compilation.WithAnalyzers([analyzer]);
var diagnostics = await withAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false);
// Filter out anything not from our analyzer id just in case
return [.. diagnostics.Where(d => d.Id == EventUsageAnalyzer.DiagnosticId)];

// Fail on unexpected diagnostics, including analyzer failures such as AD0001.
await Assert.That(diagnostics.Where(d => d.Id != EventUsageAnalyzer.DiagnosticId)).IsEmpty();
return [.. diagnostics];
}

static string LoadAnalyzedSource([CallerFilePath] string? caller = null) {
Expand All @@ -148,7 +155,8 @@ static CSharpCompilation CreateCompilation(string source, bool includeSharedAsse
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(State<>).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Aggregate<>).Assembly.Location)
MetadataReference.CreateFromFile(typeof(Aggregate<>).Assembly.Location),
MetadataReference.CreateFromFile(typeof(JetBrains.Annotations.UsedImplicitlyAttribute).Assembly.Location)
};

if (includeSharedAssembly) {
Expand All @@ -162,7 +170,7 @@ static CSharpCompilation CreateCompilation(string source, bool includeSharedAsse
var runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
refs.AddRange(Directory.GetFiles(runtimeDir, "System.*.dll").Select(dll => MetadataReference.CreateFromFile(dll)).Cast<MetadataReference>());

TryAddRef(refs, "netstandard");
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll")));

var compilation = CSharpCompilation.Create(
assemblyName: "Analyzer_Ev001_Tests_Assembly",
Expand All @@ -176,14 +184,4 @@ static CSharpCompilation CreateCompilation(string source, bool includeSharedAsse

return compilation;
}

static void TryAddRef(List<MetadataReference> refs, string asmName) {
try {
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == asmName);
if (asm != null) refs.Add(MetadataReference.CreateFromFile(asm.Location));
}
catch {
// ignore
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<!-- Analyzer input is compiled and validated by the tests. -->
<Compile Remove="Analyzed.cs" />
<None Include="Analyzed.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations"/>
<PackageReference Include="NodaTime.Serialization.SystemTextJson"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ public async Task InitializeAsync() {
var settings = KurrentDBClientSettings.Create(_esdbContainer.GetConnectionString());
Client = new(settings);
EventStore = new KurrentDBEventStore(Client);
_mongoContainer = new MongoDbBuilder().WithImage("mongo:8").Build();
// MongoDB 8 cannot start on affected Linux 6.19+ kernels used by CI runners.
// MongoDB 7.0 is unaffected: https://jira.mongodb.org/browse/SERVER-125742
_mongoContainer = new MongoDbBuilder().WithImage("mongo:7.0").Build();
await _mongoContainer.StartAsync();
var mongoSettings = MongoClientSettings.FromConnectionString(_mongoContainer.GetConnectionString());
Mongo = new MongoClient(mongoSettings).GetDatabase("bookings");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using Eventuous.Postgresql.Subscriptions;
using Eventuous.Tests.Persistence.Base.Fixtures;
using Eventuous.Tests.Subscriptions.Base;

namespace Eventuous.Tests.Postgres.Subscriptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using Eventuous.Postgresql.Subscriptions;
using Eventuous.Tests.Persistence.Base.Fixtures;
using Eventuous.Tests.Subscriptions.Base;

namespace Eventuous.Tests.Postgres.Subscriptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using Eventuous.Postgresql.Subscriptions;
using Eventuous.Tests.Persistence.Base.Fixtures;
using Eventuous.Tests.Subscriptions.Base;

namespace Eventuous.Tests.Postgres.Subscriptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Eventuous.Sqlite.Subscriptions;
using Eventuous.Subscriptions.Checkpoints;
using Eventuous.Subscriptions.Registrations;
using Eventuous.TestHelpers.TUnit.Logging;
using Eventuous.Tests.OpenTelemetry.Fakes;
using Eventuous.Tests.OpenTelemetry.Fixtures;
using Eventuous.Tests.Sqlite.Fixtures;
Expand Down
Loading