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
@@ -0,0 +1,85 @@
//-----------------------------------------------------------------------
// <copyright file="MetricsSerializersSpec.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Akka.Cluster.Metrics.Serialization;
using Akka.Configuration;
using Akka.Serialization;
using Akka.TestKit;
using FluentAssertions;
using Xunit;

namespace Akka.Cluster.Metrics.Tests
{
/// <summary>
/// AppContext switches are process-wide, so a spec that flips <c>Akka.DynamicTypeLoading</c> never runs beside another.
/// </summary>
[CollectionDefinition(Name, DisableParallelization = true)]
public sealed class DynamicTypeLoadingCollection
{
public const string Name = "Akka.DynamicTypeLoading";
}

/// <summary>
/// Keeps <see cref="MetricsSerializers"/> in sync with Akka.Cluster.Metrics' reference.conf. The shared checks
/// live in <see cref="ModuleSerializerSpecs"/>.
/// </summary>
[Collection(DynamicTypeLoadingCollection.Name)]
public class MetricsSerializersSpec : AkkaSpec
{
private static readonly Config MetricsRows = ClusterMetrics.DefaultConfig();

private static readonly Type[] BoundSamples =
{
typeof(MetricsGossipEnvelope), typeof(AdaptiveLoadBalancingPool), typeof(MixMetricsSelector),
typeof(CpuMetricsSelector), typeof(MemoryMetricsSelector)
};

public MetricsSerializersSpec(ITestOutputHelper output) : base(MetricsRows, output)
{
}

[Fact(DisplayName = "MetricsSerializers should list no serializer or type that reference.conf does not")]
public void Should_have_a_reference_conf_row_When_the_table_lists_a_type()
{
var table = new MetricsSerializers();
ModuleSerializerSpecs.AssertTableMatchesConfig(MetricsRows, table.Serializers.Select(s => s.Type), table.BoundTypes);
}

[Fact(DisplayName = "Serialization should resolve reference.conf rows spelled as Akka.Hosting writes them when dynamic type loading is off")]
public async Task Should_resolve_assembly_qualified_names_When_dynamic_type_loading_is_disabled()
=> await ModuleSerializerSpecs.AssertHostingSpellingResolves("metrics-aqn", MetricsRows, Sys);

[Fact(DisplayName = "Serialization should let application.conf override a reference.conf row when dynamic type loading is off")]
public async Task Should_honor_an_application_override_When_it_rebinds_a_Metrics_type()
{
var overrides = ConfigurationFactory.ParseString(
@"akka.actor.serialization-bindings { ""Akka.Cluster.Metrics.Serialization.MetricsGossipEnvelope, Akka.Cluster.Metrics"" = bytes }");

await ModuleSerializerSpecs.WithSystem("metrics-override", overrides, MetricsRows, system =>
{
var serialization = ModuleSerializerSpecs.BuildDefault(system, dynamicTypeLoading: false);

serialization.FindSerializerForType(typeof(MetricsGossipEnvelope)).Should().BeOfType<ByteArraySerializer>();
serialization.FindSerializerForType(typeof(AdaptiveLoadBalancingPool)).Should().BeOfType<ClusterMetricsMessageSerializer>();
});
}

[Fact(DisplayName = "Serialization should build ClusterMetricsMessageSerializer under id 10, without a warning, when dynamic type loading is off")]
public async Task Should_keep_the_metrics_serializer_id_When_dynamic_type_loading_is_disabled()
{
var serialization = await ModuleSerializerSpecs.AssertBuildsWithoutWarning(Sys, EventFilter);

serialization.GetSerializerById(10).Should().BeOfType<ClusterMetricsMessageSerializer>();
foreach (var type in BoundSamples)
serialization.FindSerializerForType(type).Should().BeOfType<ClusterMetricsMessageSerializer>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//-----------------------------------------------------------------------
// <copyright file="MetricsSerializers.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

#nullable enable
using System;
using System.Collections.Generic;
using Akka.Serialization;

namespace Akka.Cluster.Metrics.Serialization
{
/// <summary>
/// INTERNAL API. The serializer and binding rows of Akka.Cluster.Metrics' reference.conf, so they resolve without reflection.
/// </summary>
internal sealed class MetricsSerializers : ModuleSerializers
{
// the constructor reflection picks for reference.conf; ClusterMetricsMessageSerializer has one, so it always gets that one
public override IReadOnlyList<ModuleSerializer> Serializers { get; } = new[]
{
new ModuleSerializer(typeof(ClusterMetricsMessageSerializer), (system, _) => new ClusterMetricsMessageSerializer(system)),
};

public override IReadOnlyList<Type> BoundTypes { get; } = new[]
{
typeof(MetricsGossipEnvelope),
typeof(AdaptiveLoadBalancingPool),
typeof(MixMetricsSelector),
typeof(CpuMetricsSelector),
typeof(MemoryMetricsSelector),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//-----------------------------------------------------------------------
// <copyright file="ShardingSerializersSpec.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Akka.Cluster.Sharding.Serialization;
using Akka.Configuration;
using Akka.Serialization;
using Akka.TestKit;
using FluentAssertions;
using Xunit;

namespace Akka.Cluster.Sharding.Tests
{
/// <summary>
/// AppContext switches are process-wide, so a spec that flips <c>Akka.DynamicTypeLoading</c> never runs beside another.
/// </summary>
[CollectionDefinition(Name, DisableParallelization = true)]
public sealed class DynamicTypeLoadingCollection
{
public const string Name = "Akka.DynamicTypeLoading";
}

/// <summary>
/// Keeps <see cref="ShardingSerializers"/> in sync with Akka.Cluster.Sharding's reference.conf. The shared
/// checks live in <see cref="ModuleSerializerSpecs"/>.
/// </summary>
[Collection(DynamicTypeLoadingCollection.Name)]
public class ShardingSerializersSpec : AkkaSpec
{
// sharding's own rows only; ClusterSharding.DefaultConfig() also falls back to DistributedData's reference.conf
private static readonly Config ShardingRows =
ConfigurationFactory.FromResource<ClusterSharding>("Akka.Cluster.Sharding.reference.conf");

public ShardingSerializersSpec(ITestOutputHelper output) : base(ClusterSharding.DefaultConfig(), output)
{
}

[Fact(DisplayName = "ShardingSerializers should list no serializer or type that reference.conf does not")]
public void Should_have_a_reference_conf_row_When_the_table_lists_a_type()
{
var table = new ShardingSerializers();
ModuleSerializerSpecs.AssertTableMatchesConfig(ShardingRows, table.Serializers.Select(s => s.Type), table.BoundTypes);
}

[Fact(DisplayName = "Serialization should resolve reference.conf rows spelled as Akka.Hosting writes them when dynamic type loading is off")]
public async Task Should_resolve_assembly_qualified_names_When_dynamic_type_loading_is_disabled()
=> await ModuleSerializerSpecs.AssertHostingSpellingResolves("sharding-aqn", ShardingRows, Sys);

[Fact(DisplayName = "Serialization should let application.conf override a reference.conf row when dynamic type loading is off")]
public async Task Should_honor_an_application_override_When_it_rebinds_a_Sharding_type()
{
var overrides = ConfigurationFactory.ParseString(
@"akka.actor.serialization-bindings { ""Akka.Cluster.Sharding.IClusterShardingSerializable, Akka.Cluster.Sharding"" = bytes }");

await ModuleSerializerSpecs.WithSystem("sharding-override", overrides, ShardingRows, system =>
{
var serialization = ModuleSerializerSpecs.BuildDefault(system, dynamicTypeLoading: false);

serialization.FindSerializerForType(typeof(ShardRegion.StartEntity)).Should().BeOfType<ByteArraySerializer>();
});
}

[Fact(DisplayName = "Serialization should build ClusterShardingMessageSerializer under id 13, without a warning, when dynamic type loading is off")]
public async Task Should_keep_the_sharding_serializer_id_When_dynamic_type_loading_is_disabled()
{
var serialization = await ModuleSerializerSpecs.AssertBuildsWithoutWarning(Sys, EventFilter);

serialization.GetSerializerById(13).Should().BeOfType<ClusterShardingMessageSerializer>();
serialization.FindSerializerForType(typeof(ShardRegion.StartEntity)).Should().BeOfType<ClusterShardingMessageSerializer>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//-----------------------------------------------------------------------
// <copyright file="ShardingSerializers.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

#nullable enable
using System;
using System.Collections.Generic;
using Akka.Serialization;

namespace Akka.Cluster.Sharding.Serialization
{
/// <summary>
/// INTERNAL API. The serializer and binding rows of Akka.Cluster.Sharding's reference.conf, so they resolve without reflection.
/// </summary>
internal sealed class ShardingSerializers : ModuleSerializers
{
// the constructor reflection picks for reference.conf; ClusterShardingMessageSerializer has one, so it always gets that one
public override IReadOnlyList<ModuleSerializer> Serializers { get; } = new[]
{
new ModuleSerializer(typeof(ClusterShardingMessageSerializer), (system, _) => new ClusterShardingMessageSerializer(system)),
};

public override IReadOnlyList<Type> BoundTypes { get; } = new[]
{
typeof(IClusterShardingSerializable),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//-----------------------------------------------------------------------
// <copyright file="ToolsSerializersSpec.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Akka.Cluster.Tools.Client;
using Akka.Cluster.Tools.Client.Serialization;
using Akka.Cluster.Tools.PublishSubscribe;
using Akka.Cluster.Tools.PublishSubscribe.Internal;
using Akka.Cluster.Tools.PublishSubscribe.Serialization;
using Akka.Cluster.Tools.Singleton;
using Akka.Cluster.Tools.Singleton.Serialization;
using Akka.Configuration;
using Akka.Serialization;
using Akka.TestKit;
using FluentAssertions;
using Xunit;

namespace Akka.Cluster.Tools.Tests
{
/// <summary>
/// AppContext switches are process-wide, so a spec that flips <c>Akka.DynamicTypeLoading</c> never runs beside another.
/// </summary>
[CollectionDefinition(Name, DisableParallelization = true)]
public sealed class DynamicTypeLoadingCollection
{
public const string Name = "Akka.DynamicTypeLoading";
}

/// <summary>
/// Keeps <see cref="ToolsSerializers"/> in sync with Akka.Cluster.Tools' three reference.conf files
/// (Client, PublishSubscribe, Singleton). The shared checks live in <see cref="ModuleSerializerSpecs"/>.
/// </summary>
[Collection(DynamicTypeLoadingCollection.Name)]
public class ToolsSerializersSpec : AkkaSpec
{
private static readonly Config ToolsRows = ClusterClientReceptionist.DefaultConfig()
.WithFallback(DistributedPubSub.DefaultConfig())
.WithFallback(ClusterSingleton.DefaultConfig());

public ToolsSerializersSpec(ITestOutputHelper output) : base(ToolsRows, output)
{
}

[Fact(DisplayName = "ToolsSerializers should list no serializer or type that its reference.conf files do not")]
public void Should_have_a_reference_conf_row_When_the_table_lists_a_type()
{
var table = new ToolsSerializers();
ModuleSerializerSpecs.AssertTableMatchesConfig(ToolsRows, table.Serializers.Select(s => s.Type), table.BoundTypes);
}

[Fact(DisplayName = "Serialization should resolve reference.conf rows spelled as Akka.Hosting writes them when dynamic type loading is off")]
public async Task Should_resolve_assembly_qualified_names_When_dynamic_type_loading_is_disabled()
=> await ModuleSerializerSpecs.AssertHostingSpellingResolves("tools-aqn", ToolsRows, Sys);

[Fact(DisplayName = "Serialization should let application.conf override a reference.conf row when dynamic type loading is off")]
public async Task Should_honor_an_application_override_When_it_rebinds_a_Tools_type()
{
var overrides = ConfigurationFactory.ParseString(
@"akka.actor.serialization-bindings { ""Akka.Cluster.Tools.Singleton.IClusterSingletonMessage, Akka.Cluster.Tools"" = bytes }");

await ModuleSerializerSpecs.WithSystem("tools-override", overrides, ToolsRows, system =>
{
var serialization = ModuleSerializerSpecs.BuildDefault(system, dynamicTypeLoading: false);

serialization.FindSerializerForType(typeof(IClusterSingletonMessage)).Should().BeOfType<ByteArraySerializer>();
serialization.FindSerializerForType(typeof(IDistributedPubSubMessage)).Should().BeOfType<DistributedPubSubMessageSerializer>();
});
}

[Fact(DisplayName = "Serialization should build every Tools serializer under its usual id, without a warning, when dynamic type loading is off")]
public async Task Should_keep_the_Tools_serializer_ids_When_dynamic_type_loading_is_disabled()
{
var serialization = await ModuleSerializerSpecs.AssertBuildsWithoutWarning(Sys, EventFilter);

serialization.GetSerializerById(15).Should().BeOfType<ClusterClientMessageSerializer>();
serialization.GetSerializerById(9).Should().BeOfType<DistributedPubSubMessageSerializer>();
serialization.GetSerializerById(14).Should().BeOfType<ClusterSingletonMessageSerializer>();

serialization.FindSerializerForType(typeof(IClusterClientMessage)).Should().BeOfType<ClusterClientMessageSerializer>();
serialization.FindSerializerForType(typeof(IClusterClientProtocolMessage)).Should().BeOfType<ClusterClientMessageSerializer>();
serialization.FindSerializerForType(typeof(IDistributedPubSubMessage)).Should().BeOfType<DistributedPubSubMessageSerializer>();
serialization.FindSerializerForType(typeof(SendToOneSubscriber)).Should().BeOfType<DistributedPubSubMessageSerializer>();
serialization.FindSerializerForType(typeof(IClusterSingletonMessage)).Should().BeOfType<ClusterSingletonMessageSerializer>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//-----------------------------------------------------------------------
// <copyright file="ToolsSerializers.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------

#nullable enable
using System;
using System.Collections.Generic;
using Akka.Cluster.Tools.Client;
using Akka.Cluster.Tools.Client.Serialization;
using Akka.Cluster.Tools.PublishSubscribe;
using Akka.Cluster.Tools.PublishSubscribe.Internal;
using Akka.Cluster.Tools.PublishSubscribe.Serialization;
using Akka.Cluster.Tools.Singleton;
using Akka.Cluster.Tools.Singleton.Serialization;
using Akka.Serialization;

namespace Akka.Cluster.Tools
{
/// <summary>
/// INTERNAL API. The serializer and binding rows of Akka.Cluster.Tools' three reference.conf files
/// (Client, PublishSubscribe, Singleton), so they resolve without reflection.
/// </summary>
internal sealed class ToolsSerializers : ModuleSerializers
{
// the constructor reflection picks for each feature's reference.conf; each class has one constructor, so it always gets that one
public override IReadOnlyList<ModuleSerializer> Serializers { get; } = new[]
{
new ModuleSerializer(typeof(ClusterClientMessageSerializer), (system, _) => new ClusterClientMessageSerializer(system)),
new ModuleSerializer(typeof(DistributedPubSubMessageSerializer), (system, _) => new DistributedPubSubMessageSerializer(system)),
new ModuleSerializer(typeof(ClusterSingletonMessageSerializer), (system, _) => new ClusterSingletonMessageSerializer(system)),
};

public override IReadOnlyList<Type> BoundTypes { get; } = new[]
{
typeof(IClusterClientMessage),
typeof(IClusterClientProtocolMessage),
typeof(IDistributedPubSubMessage),
typeof(SendToOneSubscriber),
typeof(IClusterSingletonMessage),
};
}
}
Loading
Loading