diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddfa6ea9..e3428ae3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,19 +14,72 @@ jobs: uses: actions/setup-dotnet@v6 with: global-json-file: global.json - - run: dotnet --info - - name: Build solution and run all tests - run: dotnet test --verbosity normal -- --report-trx --results-directory '${{ env.TEST_RESULTS }}' - + - name: Build solution and run tests (excluding Nats and Redis backplane tests) + run: dotnet test --verbosity normal -- --report-trx --results-directory '${{ env.TEST_RESULTS }}' --filter-query '/*/*/(!*NatsL1*)&(!*RedisL1*)' - name: Publish 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: "Core Test results" report_individual_runs: true action_fail: true time_unit: milliseconds #ignore_runs: true compare_to_earlier_commit: false + - name: Upload core test reports + uses: actions/upload-artifact@v7 + if: ${{ always() }} + with: + name: core-test-reports + path: '${{ env.TEST_RESULTS }}/*.trx' + if-no-files-found: warn + + test-backplanes: + name: ${{ matrix.name }} L1 tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: Nats + filter: '/*/*/*NatsL1*' + - name: Redis + filter: '/*/*/*RedisL1*' + services: + nats: + image: nats:latest + ports: + - 4222:4222 + - 8222:8222 + redis: + image: redis:latest + ports: + - 6379:6379 + steps: + - uses: actions/checkout@master + - name: Set up .NET Core + uses: actions/setup-dotnet@v6 + with: + global-json-file: global.json + - run: dotnet --info + - name: Build solution and run ${{ matrix.name }}L1 tests + run: dotnet test --verbosity normal -- --report-trx --results-directory '${{ env.TEST_RESULTS }}' --filter-query '${{ matrix.filter }}' + - name: Publish ${{ matrix.name }} test results + uses: EnricoMi/publish-unit-test-result-action/linux@v2 + if: ${{ success() || failure() }} + with: + files: '${{ env.TEST_RESULTS }}/*.trx' + check_name: "${{ matrix.name }} Test results" + report_individual_runs: true + action_fail: true + time_unit: milliseconds + compare_to_earlier_commit: false + - name: Upload ${{ matrix.name }} test reports + uses: actions/upload-artifact@v7 + if: ${{ always() }} + with: + name: ${{ matrix.name }}-test-reports + path: '${{ env.TEST_RESULTS }}/*.trx' + if-no-files-found: warn diff --git a/ZiggyCreatures.FusionCache.slnx b/ZiggyCreatures.FusionCache.slnx index 24b67352..c299cb23 100644 --- a/ZiggyCreatures.FusionCache.slnx +++ b/ZiggyCreatures.FusionCache.slnx @@ -10,6 +10,7 @@ + diff --git a/global.json b/global.json index c65ce7e2..6aceed38 100644 --- a/global.json +++ b/global.json @@ -1,10 +1,10 @@ { - "sdk": { - "version": "10.0.100", - "allowPrerelease": true, - "rollForward": "latestFeature" - }, - "test": { - "runner": "Microsoft.Testing.Platform" - } + "sdk": { + "version": "10.0.100", + "allowPrerelease": true, + "rollForward": "latestFeature" + }, + "test": { + "runner": "Microsoft.Testing.Platform" + } } \ No newline at end of file diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs new file mode 100644 index 00000000..912f3946 --- /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 NATS 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 NatsBackplane 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..709ba400 --- /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..bb11c1c2 --- /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 [NATS.Net](https://github.com/nats-io/nats.net) library. \ No newline at end of file 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/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 d2d0f916..cfbb6aa4 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 42757a7a..4090a09c 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 ee38366e..9a7db17e 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj +++ b/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj @@ -37,6 +37,7 @@ +