diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3540c50..b37b5763 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,23 +10,73 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - name: Set up .NET Core - uses: actions/setup-dotnet@v4 + - name: Build solution + run: dotnet build + - name: Run core tests + run: dotnet test --no-build --verbosity normal -l trx --results-directory '${{ env.TEST_RESULTS }}' --filter "FullyQualifiedName~FusionCache&FullyQualifiedName!~NatsL1&FullyQualifiedName!~RedisL1" + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action/linux@v2 + if: ${{ success() || failure() }} with: - dotnet-version: '9.0.x' + files: '${{ env.TEST_RESULTS }}/*.trx' + check_name: "Core Test results" + report_individual_runs: true + action_fail: true + time_unit: milliseconds + #ignore_runs: true + compare_to_earlier_commit: false - - run: dotnet --info - - name: Build solution and run all tests - run: dotnet test --verbosity normal -l trx --results-directory '${{ env.TEST_RESULTS }}' + test-nats: + runs-on: ubuntu-latest + needs: build + services: + nats: + image: nats:latest + ports: + - 4222:4222 + - 8222:8222 + redis: + image: redis:latest + ports: + - 6379:6379 + steps: + - uses: actions/checkout@master + - name: Build solution + run: dotnet build + - name: Run NatsL1 tests + run: dotnet test --no-build --verbosity normal -l trx --results-directory '${{ env.TEST_RESULTS }}' --filter "FullyQualifiedName~NatsL1" + - name: Publish Nats test results + uses: EnricoMi/publish-unit-test-result-action/linux@v2 + if: ${{ success() || failure() }} + with: + files: '${{ env.TEST_RESULTS }}/*.trx' + check_name: "Nats Test results" + report_individual_runs: true + action_fail: true + time_unit: milliseconds + compare_to_earlier_commit: false - - name: Publish test results + test-redis: + runs-on: ubuntu-latest + needs: build + services: + redis: + image: redis:latest + ports: + - 6379:6379 + steps: + - uses: actions/checkout@master + - name: Build solution + run: dotnet build + - name: Run RedisL1 tests + run: dotnet test --no-build --verbosity normal -l trx --results-directory '${{ env.TEST_RESULTS }}' --filter "FullyQualifiedName~RedisL1" + - name: Publish Redis test results uses: EnricoMi/publish-unit-test-result-action/linux@v2 if: ${{ success() || failure() }} with: files: '${{ env.TEST_RESULTS }}/*.trx' - check_name: "Test results" + check_name: "Redis Test results" report_individual_runs: true action_fail: true time_unit: milliseconds - #ignore_runs: true compare_to_earlier_commit: false diff --git a/ZiggyCreatures.FusionCache.slnx b/ZiggyCreatures.FusionCache.slnx index 03c80580..140e1e06 100644 --- a/ZiggyCreatures.FusionCache.slnx +++ b/ZiggyCreatures.FusionCache.slnx @@ -9,11 +9,12 @@ + - - + + diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs new file mode 100644 index 00000000..0d63163f --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs @@ -0,0 +1,148 @@ +using System.Buffers; +using System.Text.Json; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +using NATS.Client.Core; + +using ZiggyCreatures.Caching.Fusion.Internals; + +namespace ZiggyCreatures.Caching.Fusion.Backplane.NATS; + +/// +/// A Redis based implementation of a FusionCache backplane. +/// +public partial class NatsBackplane + : IFusionCacheBackplane +{ + private BackplaneSubscriptionOptions? _subscriptionOptions; + private readonly ILogger? _logger; + private INatsConnection _connection; + private string _channelName = ""; + private Func? _incomingMessageHandlerAsync; + private INatsSub>? _subscription; + + /// + /// Initializes a new instance of the RedisBackplane class. + /// + /// The NATS connection instance to use. + /// The instance to use. If null, logging will be completely disabled. + public NatsBackplane(INatsConnection? natsConnection, ILogger? logger = null) + { + _connection = natsConnection ?? throw new ArgumentNullException(nameof(natsConnection)); + + // LOGGING + if (logger is NullLogger) + { + // IGNORE NULL LOGGER (FOR BETTER PERF) + _logger = null; + } + else + { + _logger = logger; + } + } + + /// + public async ValueTask SubscribeAsync(BackplaneSubscriptionOptions subscriptionOptions) + { + if (subscriptionOptions is null) + throw new ArgumentNullException(nameof(subscriptionOptions)); + + if (subscriptionOptions.ChannelName is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.ChannelName cannot be null"); + + if (subscriptionOptions.IncomingMessageHandler is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.IncomingMessageHandler cannot be null"); + + if (subscriptionOptions.ConnectHandler is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.ConnectHandler cannot be null"); + + if (subscriptionOptions.IncomingMessageHandlerAsync is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.IncomingMessageHandlerAsync cannot be null"); + + if (subscriptionOptions.ConnectHandlerAsync is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.ConnectHandlerAsync cannot be null"); + + _subscriptionOptions = subscriptionOptions; + + _channelName = _subscriptionOptions.ChannelName; + if (string.IsNullOrEmpty(_channelName)) + throw new NullReferenceException("The backplane channel name must have a value"); + + _incomingMessageHandlerAsync = _subscriptionOptions.IncomingMessageHandlerAsync; + _subscription = await _connection.SubscribeCoreAsync>(_channelName); + _ = Task.Run(async () => + { + while (await _subscription.Msgs.WaitToReadAsync().ConfigureAwait(false)) + { + while (_subscription.Msgs.TryRead(out var msg)) + { + using (msg.Data) + { + var message = BackplaneMessage.FromByteArray(msg.Data.Memory.ToArray()); + await OnMessageAsync(message).ConfigureAwait(false); + } + } + } + }); + } + + + /// + public void Subscribe(BackplaneSubscriptionOptions options) + { +#pragma warning disable VSTHRD002 // Suppressing since this is a sync-over-async method intentionally as the library doesn't provide sync APIs + SubscribeAsync(options).AsTask().Wait(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + } + + /// + public async ValueTask UnsubscribeAsync() + { + if (_subscription is not null) + { + await _subscription.UnsubscribeAsync().ConfigureAwait(false); + await _subscription.Msgs.Completion; + } + } + + /// + public void Unsubscribe() + { +#pragma warning disable VSTHRD002 // Suppressing since this is a sync-over-async method intentionally as the library doesn't provide sync APIs + UnsubscribeAsync().AsTask().Wait(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + } + + /// + public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + { + var writer = new NatsBufferWriter(); + writer.Write(BackplaneMessage.ToByteArray(message)); + await _connection.PublishAsync(_channelName, writer).ConfigureAwait(false); + } + + /// + public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + { +#pragma warning disable VSTHRD002 // Suppressing since this is a sync-over-async method intentionally as the library doesn't provide sync APIs + PublishAsync(message, options, token).AsTask().Wait(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + } + + internal async ValueTask OnMessageAsync(BackplaneMessage message) + { + var tmp = _incomingMessageHandlerAsync; + if (tmp is null) + { + if (_logger?.IsEnabled(LogLevel.Trace) ?? false) + _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}]: [BP] incoming message handler was null", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId); + return; + } + + await tmp(message).ConfigureAwait(false); + } +} diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj b/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj new file mode 100644 index 00000000..a1cfad0d --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj @@ -0,0 +1,25 @@ + + + netstandard2.0;net8.0;net9.0 + 2.5.0 + ZiggyCreatures.FusionCache.Backplane.NATS + FusionCache backplane for NATS based on the NATS.Net library + backplane;nats;synadia;caching;cache;hybrid;hybrid-cache;hybridcache;multi-level;multilevel;fusion;fusioncache;fusion-cache;performance;async;ziggy + ZiggyCreatures.Caching.Fusion.Backplane.NATS + true + 1.0.0 + + + + + + + + + + + + + + + diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/artwork/logo-128x128.png b/src/ZiggyCreatures.FusionCache.Backplane.NATS/artwork/logo-128x128.png new file mode 100644 index 00000000..ce400a79 Binary files /dev/null and b/src/ZiggyCreatures.FusionCache.Backplane.NATS/artwork/logo-128x128.png differ diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/docs/README.md b/src/ZiggyCreatures.FusionCache.Backplane.NATS/docs/README.md new file mode 100644 index 00000000..e529a7ba --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/docs/README.md @@ -0,0 +1,13 @@ +# FusionCache + +![FusionCache logo](https://raw.githubusercontent.com/ZiggyCreatures/FusionCache/main/docs/logo-256x256.png) + +### FusionCache is an easy to use, fast and robust hybrid cache with advanced resiliency features. + +It was born after years of dealing with all sorts of different types of caches: memory caching, distributed caching, http caching, CDNs, browser cache, offline cache, you name it. So I've tried to put together these experiences and came up with FusionCache. + +Find out [more](https://github.com/ZiggyCreatures/FusionCache). + +## 📦 This package + +This package is a backplane implementation on [NATS](https://nats.io/) based on the awesome [StackExchange.Redis](https://github.com/StackExchange/StackExchange.Redis) library. \ No newline at end of file diff --git a/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane.cs index b133caf4..934909d8 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; + using StackExchange.Redis; namespace ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; diff --git a/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs b/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs index aa2f9a0c..739c46ff 100644 --- a/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs +++ b/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Text; + using ZiggyCreatures.Caching.Fusion.Internals; namespace ZiggyCreatures.Caching.Fusion.Backplane; diff --git a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs index a3d3de0c..96948d54 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs @@ -1,5 +1,7 @@ using System.Diagnostics; + using Microsoft.Extensions.Logging; + using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Internals.Diagnostics; diff --git a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs index e94ba9c4..fe1c7e2c 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs @@ -1,5 +1,7 @@ using System.Diagnostics; + using Microsoft.Extensions.Logging; + using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Internals.Diagnostics; diff --git a/src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs b/src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs new file mode 100644 index 00000000..40e4f5c2 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; + +namespace ZiggyCreatures.Caching.Fusion.Internals +{ + internal static class EncodingExtensions + { +#if NETSTANDARD2_0 + public static int GetBytes(this T encoding, string s, Span span) where T : Encoding + { + int byteCount = encoding.GetByteCount(s); + byte[] stringBytes = ArrayPool.Shared.Rent(byteCount); + try + { + encoding.GetBytes(s, 0, s.Length, stringBytes, 0); + stringBytes.AsSpan(0, byteCount).CopyTo(span); + return byteCount; + } + finally + { + ArrayPool.Shared.Return(stringBytes); + } + } + + public static string GetString(this T encoding, ReadOnlySpan bytes) where T : Encoding + { + byte[] stringBytes = ArrayPool.Shared.Rent(bytes.Length); + try + { + bytes.CopyTo(stringBytes); + return encoding.GetString(stringBytes, 0, bytes.Length); + } + finally + { + ArrayPool.Shared.Return(stringBytes); + } + } +#endif + } +} diff --git a/tests/AOTTester/AOTTester.csproj b/tests/AOTTester/AOTTester.csproj index 7ba3f559..4f8660f0 100644 --- a/tests/AOTTester/AOTTester.csproj +++ b/tests/AOTTester/AOTTester.csproj @@ -2,7 +2,7 @@ Exe - net10.0 + net9.0 enable enable true diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs index b30e0ad8..e52fd4f1 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs @@ -1,26 +1,29 @@ using FusionCacheTests.Stuff; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; + +using NATS.Client.Core; + using Xunit; using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Backplane.Memory; +using ZiggyCreatures.Caching.Fusion.Backplane.NATS; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.DangerZone; namespace FusionCacheTests; -public partial class L1BackplaneTests - : AbstractTests +public abstract partial class L1BackplaneTests : AbstractTests where T : IFusionCacheBackplane { - public L1BackplaneTests(ITestOutputHelper output) - : base(output, "MyCache:") - { - if (UseRedis) - InitialBackplaneDelay = TimeSpan.FromSeconds(5).PlusALittleBit(); + protected TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); + protected TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); + + protected L1BackplaneTests(ITestOutputHelper output) : base(output, "MyCache:") + { } - private FusionCacheOptions CreateFusionCacheOptions() + protected virtual FusionCacheOptions CreateFusionCacheOptions() { var res = new FusionCacheOptions { @@ -32,21 +35,9 @@ private FusionCacheOptions CreateFusionCacheOptions() return res; } - private static readonly bool UseRedis = false; - private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; - - private readonly TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); - private readonly TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); - - private IFusionCacheBackplane CreateBackplane(string connectionId, ILogger? logger = null) - { - if (UseRedis) - return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: (logger as ILogger) ?? CreateXUnitLogger()); - - return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: (logger as ILogger) ?? CreateXUnitLogger()); - } + protected abstract T CreateBackplane(string connectionId, ILogger? logger = null); - private FusionCache CreateFusionCache(string? cacheName, IFusionCacheBackplane? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null, ILogger? logger = null) + protected FusionCache CreateFusionCache(string? cacheName, T? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null, ILogger? logger = null) { var options = CreateFusionCacheOptions(); @@ -67,3 +58,53 @@ private FusionCache CreateFusionCache(string? cacheName, IFusionCacheBackplane? return fusionCache; } } + +public class RedisL1BackplaneTests : L1BackplaneTests +{ + private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + public RedisL1BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override RedisBackplane CreateBackplane(string connectionId, ILogger? logger = null) + { + return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: (logger as ILogger) ?? CreateXUnitLogger()); + } +} + +public class MemoryL1BackplaneTests : L1BackplaneTests +{ + public MemoryL1BackplaneTests(ITestOutputHelper output) : base(output) + { + } + + protected override MemoryBackplane CreateBackplane(string connectionId, ILogger? logger = null) + { + return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: (logger as ILogger) ?? CreateXUnitLogger()); + } +} + +public class NatsL1BackplaneTests : L1BackplaneTests +{ + private static readonly string NatsConnection = "nats://localhost:4222"; + + public NatsL1BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override FusionCacheOptions CreateFusionCacheOptions() + { + var options = base.CreateFusionCacheOptions(); + options.InternalStrings.SetToSafeStrings(); + return options; + } + + protected override NatsBackplane CreateBackplane(string connectionId, ILogger? logger = null) + { + var natsConnection = new NatsConnection(new NatsOpts() { Url = NatsConnection }); + return new NatsBackplane(natsConnection, logger: (logger as ILogger) ?? CreateXUnitLogger()); + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs index 2b9130ef..82885c82 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs @@ -8,7 +8,7 @@ namespace FusionCacheTests; -public partial class L1BackplaneTests +public partial class L1BackplaneTests { [Fact] public async Task WorksWithDifferentCachesAsync() diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs index 01015744..542ff0d7 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs @@ -8,7 +8,7 @@ namespace FusionCacheTests; -public partial class L1BackplaneTests +public partial class L1BackplaneTests { [Fact] public void WorksWithDifferentCaches() diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs index 265e6f40..f98fbc8e 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs @@ -3,26 +3,29 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Options; + +using NATS.Client.Core; + using Xunit; using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Backplane.Memory; +using ZiggyCreatures.Caching.Fusion.Backplane.NATS; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.DangerZone; namespace FusionCacheTests; -public partial class L1L2BackplaneTests - : AbstractTests +public abstract partial class L1L2BackplaneTests : AbstractTests where TBackplane : class, IFusionCacheBackplane where TDistributedCache : IDistributedCache { - public L1L2BackplaneTests(ITestOutputHelper output) - : base(output, "MyCache:") + protected TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); + protected TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); + + protected L1L2BackplaneTests(ITestOutputHelper output) : base(output, "MyCache:") { - if (UseRedis) - InitialBackplaneDelay = TimeSpan.FromSeconds(5).PlusALittleBit(); } - private FusionCacheOptions CreateFusionCacheOptions() + protected virtual FusionCacheOptions CreateFusionCacheOptions() { var res = new FusionCacheOptions { @@ -33,30 +36,11 @@ private FusionCacheOptions CreateFusionCacheOptions() return res; } - - private static readonly bool UseRedis = false; - private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; - - private readonly TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); - private readonly TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); - - private IFusionCacheBackplane CreateBackplane(string connectionId) - { - if (UseRedis) - return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: CreateXUnitLogger()); - - return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: CreateXUnitLogger()); - } - - private static IDistributedCache CreateDistributedCache() - { - if (UseRedis) - return new RedisCache(new RedisCacheOptions { Configuration = RedisConnection }); - - return new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - } - - private FusionCache CreateFusionCache(string? cacheName, SerializerType? serializerType, IDistributedCache? distributedCache, IFusionCacheBackplane? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null) + + protected abstract TBackplane CreateBackplane(string connectionId); + protected abstract TDistributedCache CreateDistributedCache(); + + protected FusionCache CreateFusionCache(string? cacheName, SerializerType? serializerType, TDistributedCache? distributedCache, TBackplane? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null) { var options = CreateFusionCacheOptions(); @@ -83,3 +67,67 @@ private FusionCache CreateFusionCache(string? cacheName, SerializerType? seriali return fusionCache; } } + +public class RedisL1L2BackplaneTests : L1L2BackplaneTests +{ + private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + public RedisL1L2BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override RedisBackplane CreateBackplane(string connectionId) + { + return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: CreateXUnitLogger()); + } + + protected override RedisCache CreateDistributedCache() + { + return new RedisCache(new RedisCacheOptions { Configuration = RedisConnection }); + } +} + +public class MemoryL1L2BackplaneTests : L1L2BackplaneTests +{ + public MemoryL1L2BackplaneTests(ITestOutputHelper output) : base(output) + { + } + protected override MemoryBackplane CreateBackplane(string connectionId) + { + return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: CreateXUnitLogger()); + } + protected override MemoryDistributedCache CreateDistributedCache() + { + return new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); + } +} + +public class NatsL1L2BackplaneTests : L1L2BackplaneTests +{ + private static readonly string NatsConnection = "nats://localhost:4222"; + private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + public NatsL1L2BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override FusionCacheOptions CreateFusionCacheOptions() + { + var options = base.CreateFusionCacheOptions(); + options.InternalStrings.SetToSafeStrings(); + return options; + } + + protected override NatsBackplane CreateBackplane(string connectionId) + { + var natsConnection = new NatsConnection(new NatsOpts() { Url = NatsConnection }); + return new NatsBackplane(natsConnection, logger: CreateXUnitLogger()); + } + + protected override RedisCache CreateDistributedCache() + { + return new RedisCache(new RedisCacheOptions { Configuration = RedisConnection }); + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs index 16e9d25f..e293dc9e 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs @@ -14,7 +14,7 @@ namespace FusionCacheTests; -public partial class L1L2BackplaneTests +public partial class L1L2BackplaneTests { [Theory] [ClassData(typeof(SerializerTypesClassData))] diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs index 7e9bee11..4e8ffafe 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs @@ -14,7 +14,7 @@ namespace FusionCacheTests; -public partial class L1L2BackplaneTests +public partial class L1L2BackplaneTests { [Theory] [ClassData(typeof(SerializerTypesClassData))] diff --git a/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj b/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj index 153ded50..4635a2ee 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj +++ b/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj @@ -35,6 +35,7 @@ +