diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 1c55b29f2..e9877fe2b 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -13,10 +13,6 @@ From 3.0, [release notes will be maintained in GitHub only](https://github.com/S --- -## 2.12.14 - -- (none) - ## 2.13.17 - Fix TCP platform-dependent TCP keep-alive problems, and make an explicit option. ([#3090 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/3090)) diff --git a/src/RESPite/PublicAPI/PublicAPI.Shipped.txt b/src/RESPite/PublicAPI/PublicAPI.Shipped.txt index f5ab197bf..412c52862 100644 --- a/src/RESPite/PublicAPI/PublicAPI.Shipped.txt +++ b/src/RESPite/PublicAPI/PublicAPI.Shipped.txt @@ -210,3 +210,13 @@ [SER004]virtual RESPite.Messages.RespReader.Projection.Invoke(ref TState state, ref RESPite.Messages.RespReader value) -> TResult [SER004]virtual RESPite.Messages.RespReader.ScalarParser.Invoke(scoped System.ReadOnlySpan value, out TValue result) -> bool [SER004]RESPite.Messages.RespReader.Serialize() -> byte[]! +[SER004]RESPite.Messages.RespReader.TryParseScalar(RESPite.Messages.RespReader.ScalarParser! parser, out T value) -> bool +[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool +[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool +[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool +[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool +[SER004]virtual RESPite.Buffers.CycleBufferPool.Rent(in System.Buffers.ReadOnlySequence existing) -> System.Buffers.IMemoryOwner! +[SER004]RESPite.Buffers.CycleBuffer.Pool.get -> System.Buffers.MemoryPool! +[SER004]RESPite.Buffers.CycleBufferPool +[SER004]RESPite.Buffers.CycleBufferPool.CycleBufferPool() -> void +[SER004]static RESPite.Buffers.CycleBuffer.Create(System.Buffers.MemoryPool? pool = null, RESPite.Buffers.ICycleBufferCallback? callback = null) -> RESPite.Buffers.CycleBuffer diff --git a/src/RESPite/PublicAPI/PublicAPI.Unshipped.txt b/src/RESPite/PublicAPI/PublicAPI.Unshipped.txt index f91f0fee0..7dc5c5811 100644 --- a/src/RESPite/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/RESPite/PublicAPI/PublicAPI.Unshipped.txt @@ -1,11 +1 @@ -#nullable enable -[SER004]RESPite.Messages.RespReader.TryParseScalar(RESPite.Messages.RespReader.ScalarParser! parser, out T value) -> bool -[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool -[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool -[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool -[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan first, System.ReadOnlySpan second) -> bool -[SER004]virtual RESPite.Buffers.CycleBufferPool.Rent(in System.Buffers.ReadOnlySequence existing) -> System.Buffers.IMemoryOwner! -[SER004]RESPite.Buffers.CycleBuffer.Pool.get -> System.Buffers.MemoryPool! -[SER004]RESPite.Buffers.CycleBufferPool -[SER004]RESPite.Buffers.CycleBufferPool.CycleBufferPool() -> void -[SER004]static RESPite.Buffers.CycleBuffer.Create(System.Buffers.MemoryPool? pool = null, RESPite.Buffers.ICycleBufferCallback? callback = null) -> RESPite.Buffers.CycleBuffer +#nullable enable diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 269cc11d2..eabff622b 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -2517,3 +2517,26 @@ virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.TcpKeepAlive.ge [SER006]StackExchange.Redis.ArrayInfo.ArrayInfo(scoped System.ReadOnlySpan> values) -> void [SER006]StackExchange.Redis.ArrayInfo.ToDictionary() -> System.Collections.Generic.Dictionary! [SER006]StackExchange.Redis.ArrayInfo.this[string! key].get -> StackExchange.Redis.RedisValue +StackExchange.Redis.ConfigurationOptions.RequestBufferPool.get -> System.Buffers.MemoryPool? +StackExchange.Redis.ConfigurationOptions.RequestBufferPool.set -> void +StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.get -> System.Buffers.MemoryPool? +StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.set -> void +static StackExchange.Redis.Lease.Create(int length, System.Buffers.MemoryPool? pool, bool clear = true) -> StackExchange.Redis.Lease! +[SER005]StackExchange.Redis.TestHarness +[SER005]StackExchange.Redis.TestHarness.BufferValidator +[SER005]StackExchange.Redis.TestHarness.ChannelPrefix.get -> StackExchange.Redis.RedisChannel +[SER005]StackExchange.Redis.TestHarness.CommandMap.get -> StackExchange.Redis.CommandMap! +[SER005]StackExchange.Redis.TestHarness.KeyPrefix.get -> StackExchange.Redis.RedisKey +[SER005]StackExchange.Redis.TestHarness.Read(System.ReadOnlySpan value) -> StackExchange.Redis.RedisResult! +[SER005]StackExchange.Redis.TestHarness.TestHarness(StackExchange.Redis.CommandMap? commandMap = null, StackExchange.Redis.RedisChannel channelPrefix = default(StackExchange.Redis.RedisChannel), StackExchange.Redis.RedisKey keyPrefix = default(StackExchange.Redis.RedisKey)) -> void +[SER005]StackExchange.Redis.TestHarness.ValidateResp(System.ReadOnlySpan expected, string! command, params System.Collections.Generic.ICollection! args) -> void +[SER005]StackExchange.Redis.TestHarness.ValidateResp(string! expected, string! command, params System.Collections.Generic.ICollection! args) -> void +[SER005]StackExchange.Redis.TestHarness.ValidateRouting(in StackExchange.Redis.RedisKey expected, params System.Collections.Generic.ICollection! args) -> void +[SER005]StackExchange.Redis.TestHarness.Write(System.Buffers.IBufferWriter! target, string! command, params System.Collections.Generic.ICollection! args) -> void +[SER005]StackExchange.Redis.TestHarness.Write(string! command, params System.Collections.Generic.ICollection! args) -> byte[]! +[SER005]static StackExchange.Redis.TestHarness.AssertEqual(System.ReadOnlySpan expected, System.ReadOnlySpan actual, System.Action, System.ReadOnlyMemory>! handler) -> void +[SER005]static StackExchange.Redis.TestHarness.AssertEqual(string! expected, System.ReadOnlySpan actual, System.Action! handler) -> void +[SER005]virtual StackExchange.Redis.TestHarness.BufferValidator.Invoke(scoped System.ReadOnlySpan buffer) -> void +[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(System.ReadOnlyMemory expected, System.ReadOnlyMemory actual) -> void +[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(in StackExchange.Redis.RedisKey expected, in StackExchange.Redis.RedisKey actual) -> void +[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(string! expected, string! actual) -> void diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 0f75682cb..7dc5c5811 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1,24 +1 @@ -#nullable enable -StackExchange.Redis.ConfigurationOptions.RequestBufferPool.get -> System.Buffers.MemoryPool? -StackExchange.Redis.ConfigurationOptions.RequestBufferPool.set -> void -StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.get -> System.Buffers.MemoryPool? -StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.set -> void -static StackExchange.Redis.Lease.Create(int length, System.Buffers.MemoryPool? pool, bool clear = true) -> StackExchange.Redis.Lease! -[SER005]StackExchange.Redis.TestHarness -[SER005]StackExchange.Redis.TestHarness.BufferValidator -[SER005]StackExchange.Redis.TestHarness.ChannelPrefix.get -> StackExchange.Redis.RedisChannel -[SER005]StackExchange.Redis.TestHarness.CommandMap.get -> StackExchange.Redis.CommandMap! -[SER005]StackExchange.Redis.TestHarness.KeyPrefix.get -> StackExchange.Redis.RedisKey -[SER005]StackExchange.Redis.TestHarness.Read(System.ReadOnlySpan value) -> StackExchange.Redis.RedisResult! -[SER005]StackExchange.Redis.TestHarness.TestHarness(StackExchange.Redis.CommandMap? commandMap = null, StackExchange.Redis.RedisChannel channelPrefix = default(StackExchange.Redis.RedisChannel), StackExchange.Redis.RedisKey keyPrefix = default(StackExchange.Redis.RedisKey)) -> void -[SER005]StackExchange.Redis.TestHarness.ValidateResp(System.ReadOnlySpan expected, string! command, params System.Collections.Generic.ICollection! args) -> void -[SER005]StackExchange.Redis.TestHarness.ValidateResp(string! expected, string! command, params System.Collections.Generic.ICollection! args) -> void -[SER005]StackExchange.Redis.TestHarness.ValidateRouting(in StackExchange.Redis.RedisKey expected, params System.Collections.Generic.ICollection! args) -> void -[SER005]StackExchange.Redis.TestHarness.Write(System.Buffers.IBufferWriter! target, string! command, params System.Collections.Generic.ICollection! args) -> void -[SER005]StackExchange.Redis.TestHarness.Write(string! command, params System.Collections.Generic.ICollection! args) -> byte[]! -[SER005]static StackExchange.Redis.TestHarness.AssertEqual(System.ReadOnlySpan expected, System.ReadOnlySpan actual, System.Action, System.ReadOnlyMemory>! handler) -> void -[SER005]static StackExchange.Redis.TestHarness.AssertEqual(string! expected, System.ReadOnlySpan actual, System.Action! handler) -> void -[SER005]virtual StackExchange.Redis.TestHarness.BufferValidator.Invoke(scoped System.ReadOnlySpan buffer) -> void -[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(System.ReadOnlyMemory expected, System.ReadOnlyMemory actual) -> void -[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(in StackExchange.Redis.RedisKey expected, in StackExchange.Redis.RedisKey actual) -> void -[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(string! expected, string! actual) -> void +#nullable enable diff --git a/tests/StackExchange.Redis.Tests/ClusterTests.cs b/tests/StackExchange.Redis.Tests/ClusterTests.cs index 016480c59..118e0f975 100644 --- a/tests/StackExchange.Redis.Tests/ClusterTests.cs +++ b/tests/StackExchange.Redis.Tests/ClusterTests.cs @@ -146,6 +146,7 @@ public async Task TestIdentity() [Fact] public async Task IntentionalWrongServer() { + SkipOnWindowsRelease(); static string? StringGet(IServer server, RedisKey key, CommandFlags flags = CommandFlags.None) => (string?)server.Execute(0, "GET", [key], flags); diff --git a/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs b/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs index e967de388..258110a6e 100644 --- a/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs @@ -8,22 +8,35 @@ namespace StackExchange.Redis.Tests; [RunPerProtocol] public class KeyIdleAsyncTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) { + // Target the standalone secure server (6381) rather than the default primary (6379). + // OBJECT IDLETIME is reset via the value's LRU access clock, but Redis deliberately + // suppresses that update while a save/AOF/replication-sync child is active (copy-on-write + // avoidance). The default primary has a replica attached, and every replica full-sync forks + // such a child; if one overlaps a touch/read in these tests, the idle time isn't reset and the + // test flakes (observed on Windows CI). 6381 has no replica and no persistence, so no fork ever + // suppresses the reset and the behaviour is deterministic. Overriding GetConfiguration here also + // opts these tests out of the shared connection fixture, giving each a dedicated 6381 connection. + protected override string GetConfiguration() + => TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword; + [Fact] public async Task IdleTimeAsync() { + SkipOnWindowsRelease("WSL exacerbates replication time"); await using var conn = Create(); RedisKey key = Me(); var db = conn.GetDatabase(); db.KeyDelete(key, CommandFlags.FireAndForget); db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + var timer = Stopwatch.StartNew(); await Task.Delay(2000).ForAwait(); var idleTime = await db.KeyIdleTimeAsync(key).ForAwait(); - Assert.True(idleTime > TimeSpan.Zero, "First check"); + Assert.True(idleTime > TimeSpan.Zero, $"First check: {idleTime} should be > 0; elapsed: {timer.ElapsedMilliseconds}ms"); db.StringSet(key, "new value2", flags: CommandFlags.FireAndForget); var idleTime2 = await db.KeyIdleTimeAsync(key).ForAwait(); - Assert.True(idleTime2 < idleTime, "Second check"); + Assert.True(idleTime2 < idleTime, $"Second check: {idleTime2} should be < {idleTime}; elapsed: {timer.ElapsedMilliseconds}ms"); db.KeyDelete(key); var idleTime3 = await db.KeyIdleTimeAsync(key).ForAwait(); @@ -33,6 +46,7 @@ public async Task IdleTimeAsync() [Fact] public async Task TouchIdleTimeAsync() { + SkipOnWindowsRelease("WSL exacerbates replication time"); await using var conn = Create(require: RedisFeatures.v3_2_1); RedisKey key = Me(); diff --git a/tests/StackExchange.Redis.Tests/KeyIdleTests.cs b/tests/StackExchange.Redis.Tests/KeyIdleTests.cs index deec1efb4..9c408b4e8 100644 --- a/tests/StackExchange.Redis.Tests/KeyIdleTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyIdleTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using Xunit; @@ -7,22 +8,35 @@ namespace StackExchange.Redis.Tests; [RunPerProtocol] public class KeyIdleTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) { + // Target the standalone secure server (6381) rather than the default primary (6379). + // OBJECT IDLETIME is reset via the value's LRU access clock, but Redis deliberately + // suppresses that update while a save/AOF/replication-sync child is active (copy-on-write + // avoidance). The default primary has a replica attached, and every replica full-sync forks + // such a child; if one overlaps a touch/read in these tests, the idle time isn't reset and the + // test flakes (observed on Windows CI). 6381 has no replica and no persistence, so no fork ever + // suppresses the reset and the behaviour is deterministic. Overriding GetConfiguration here also + // opts these tests out of the shared connection fixture, giving each a dedicated 6381 connection. + protected override string GetConfiguration() + => TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword; + [Fact] public async Task IdleTime() { + SkipOnWindowsRelease("WSL exacerbates replication time"); await using var conn = Create(); RedisKey key = Me(); var db = conn.GetDatabase(); db.KeyDelete(key, CommandFlags.FireAndForget); db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + var timer = Stopwatch.StartNew(); await Task.Delay(2000).ForAwait(); var idleTime = db.KeyIdleTime(key); - Assert.True(idleTime > TimeSpan.Zero); + Assert.True(idleTime > TimeSpan.Zero, $"First check: {idleTime} should be > 0; elapsed: {timer.ElapsedMilliseconds}ms"); db.StringSet(key, "new value2", flags: CommandFlags.FireAndForget); var idleTime2 = db.KeyIdleTime(key); - Assert.True(idleTime2 < idleTime); + Assert.True(idleTime2 < idleTime, $"Second check: {idleTime2} should be < {idleTime}; elapsed: {timer.ElapsedMilliseconds}ms"); db.KeyDelete(key); var idleTime3 = db.KeyIdleTime(key); @@ -32,18 +46,20 @@ public async Task IdleTime() [Fact] public async Task TouchIdleTime() { + SkipOnWindowsRelease("WSL exacerbates replication time"); await using var conn = Create(require: RedisFeatures.v3_2_1); RedisKey key = Me(); var db = conn.GetDatabase(); db.KeyDelete(key, CommandFlags.FireAndForget); db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + var timer = Stopwatch.StartNew(); await Task.Delay(2000).ForAwait(); var idleTime = db.KeyIdleTime(key); - Assert.True(idleTime > TimeSpan.Zero, "First check"); + Assert.True(idleTime > TimeSpan.Zero, $"First check: {idleTime} should be > 0; elapsed: {timer.ElapsedMilliseconds}ms"); - Assert.True(db.KeyTouch(key), "Second check"); + Assert.True(db.KeyTouch(key), $"Second check: should be True; elapsed: {timer.ElapsedMilliseconds}ms"); var idleTime1 = db.KeyIdleTime(key); - Assert.True(idleTime1 < idleTime, "Third check"); + Assert.True(idleTime1 < idleTime, $"Third check: {idleTime1} should be < {idleTime}; elapsed: {timer.ElapsedMilliseconds}ms"); } } diff --git a/tests/StackExchange.Redis.Tests/RoleTests.cs b/tests/StackExchange.Redis.Tests/RoleTests.cs index 198ae6da7..92f66e0e4 100644 --- a/tests/StackExchange.Redis.Tests/RoleTests.cs +++ b/tests/StackExchange.Redis.Tests/RoleTests.cs @@ -13,6 +13,7 @@ public class Roles(ITestOutputHelper output, SharedConnectionFixture fixture) : [InlineData(false)] public async Task PrimaryRole(bool allowAdmin) // should work with or without admin now { + SkipOnWindowsRelease(); await using var conn = Create(allowAdmin: allowAdmin); var servers = conn.GetServers(); Log("Server list:"); diff --git a/tests/StackExchange.Redis.Tests/SentinelTests.cs b/tests/StackExchange.Redis.Tests/SentinelTests.cs index 039df4335..a53194169 100644 --- a/tests/StackExchange.Redis.Tests/SentinelTests.cs +++ b/tests/StackExchange.Redis.Tests/SentinelTests.cs @@ -12,6 +12,7 @@ public class SentinelTests(ITestOutputHelper output) : SentinelBase(output) [Fact] public async Task PrimaryConnectTest() { + SkipOnWindowsRelease(); var connectionString = $"{TestConfig.Current.SentinelServer},serviceName={ServiceOptions.ServiceName},allowAdmin=true"; var conn = ConnectionMultiplexer.Connect(connectionString); @@ -49,6 +50,7 @@ public async Task PrimaryConnectTest() [Fact] public async Task PrimaryConnectAsyncTest() { + SkipOnWindowsRelease(); var connectionString = $"{TestConfig.Current.SentinelServer},serviceName={ServiceOptions.ServiceName},allowAdmin=true"; var conn = await ConnectionMultiplexer.ConnectAsync(connectionString); @@ -86,6 +88,7 @@ public async Task PrimaryConnectAsyncTest() [RunPerProtocol] public async Task SentinelConnectTest() { + SkipOnWindowsRelease(); var options = ServiceOptions.Clone(); options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA); await using var conn = ConnectionMultiplexer.SentinelConnect(options); @@ -98,6 +101,7 @@ public async Task SentinelConnectTest() [Fact] public async Task SentinelRepeatConnectTest() { + SkipOnWindowsRelease(); var options = ConfigurationOptions.Parse($"{TestConfig.Current.SentinelServer}:{TestConfig.Current.SentinelPortA}"); options.ServiceName = ServiceName; options.AllowAdmin = true; @@ -130,6 +134,7 @@ public async Task SentinelRepeatConnectTest() [Fact] public async Task SentinelConnectAsyncTest() { + SkipOnWindowsRelease(); var options = ServiceOptions.Clone(); options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA); var conn = await ConnectionMultiplexer.SentinelConnectAsync(options); @@ -142,6 +147,7 @@ public async Task SentinelConnectAsyncTest() [Fact] public void SentinelRole() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var role = server.Role(); @@ -155,6 +161,7 @@ public void SentinelRole() [Fact] public async Task PingTest() { + SkipOnWindowsRelease(); var test = await SentinelServerA.PingAsync(); Log("ping to sentinel {0}:{1} took {2} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA, test.TotalMilliseconds); test = await SentinelServerB.PingAsync(); @@ -166,6 +173,7 @@ public async Task PingTest() [Fact] public void SentinelGetPrimaryAddressByNameTest() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var primary = server.SentinelMaster(ServiceName); @@ -182,6 +190,7 @@ public void SentinelGetPrimaryAddressByNameTest() [Fact] public async Task SentinelGetPrimaryAddressByNameAsyncTest() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var primary = server.SentinelMaster(ServiceName); @@ -198,6 +207,7 @@ public async Task SentinelGetPrimaryAddressByNameAsyncTest() [Fact] public void SentinelGetMasterAddressByNameNegativeTest() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var endpoint = server.SentinelGetMasterAddressByName("FakeServiceName"); @@ -208,6 +218,7 @@ public void SentinelGetMasterAddressByNameNegativeTest() [Fact] public async Task SentinelGetMasterAddressByNameAsyncNegativeTest() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var endpoint = await server.SentinelGetMasterAddressByNameAsync("FakeServiceName").ForAwait(); @@ -218,6 +229,7 @@ public async Task SentinelGetMasterAddressByNameAsyncNegativeTest() [Fact] public void SentinelPrimaryTest() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var dict = server.SentinelMaster(ServiceName).ToDictionary(); @@ -233,6 +245,7 @@ public void SentinelPrimaryTest() [Fact] public async Task SentinelPrimaryAsyncTest() { + SkipOnWindowsRelease(); foreach (var server in SentinelsServers) { var results = await server.SentinelMasterAsync(ServiceName).ForAwait(); @@ -248,6 +261,7 @@ public async Task SentinelPrimaryAsyncTest() [Fact] public void SentinelSentinelsTest() { + SkipOnWindowsRelease(); var sentinels = SentinelServerA.SentinelSentinels(ServiceName); var expected = new List @@ -305,6 +319,7 @@ public void SentinelSentinelsTest() [Fact] public async Task SentinelSentinelsAsyncTest() { + SkipOnWindowsRelease(); var sentinels = await SentinelServerA.SentinelSentinelsAsync(ServiceName).ForAwait(); var expected = new List { @@ -363,6 +378,7 @@ public async Task SentinelSentinelsAsyncTest() [Fact] public void SentinelPrimariesTest() { + SkipOnWindowsRelease(); var primaryConfigs = SentinelServerA.SentinelMasters(); Assert.Single(primaryConfigs); Assert.True(primaryConfigs[0].ToDictionary().ContainsKey("name"), "replicaConfigs contains 'name'"); @@ -380,6 +396,7 @@ public void SentinelPrimariesTest() [Fact] public async Task SentinelPrimariesAsyncTest() { + SkipOnWindowsRelease(); var primaryConfigs = await SentinelServerA.SentinelMastersAsync().ForAwait(); Assert.Single(primaryConfigs); Assert.True(primaryConfigs[0].ToDictionary().ContainsKey("name"), "replicaConfigs contains 'name'"); @@ -397,6 +414,7 @@ public async Task SentinelPrimariesAsyncTest() [Fact] public async Task SentinelReplicasTest() { + SkipOnWindowsRelease(); // Give previous test run a moment to reset when multi-framework failover is in play. await UntilConditionAsync(TimeSpan.FromSeconds(5), () => SentinelServerA.SentinelReplicas(ServiceName).Length > 0); @@ -417,6 +435,7 @@ public async Task SentinelReplicasTest() [Fact] public async Task SentinelReplicasAsyncTest() { + SkipOnWindowsRelease(); // Give previous test run a moment to reset when multi-framework failover is in play. await UntilConditionAsync(TimeSpan.FromSeconds(5), () => SentinelServerA.SentinelReplicas(ServiceName).Length > 0); @@ -436,6 +455,7 @@ public async Task SentinelReplicasAsyncTest() [Fact] public async Task SentinelGetSentinelAddressesTest() { + SkipOnWindowsRelease(); var addresses = await SentinelServerA.SentinelGetSentinelAddressesAsync(ServiceName).ForAwait(); Assert.Contains(SentinelServerB.EndPoint, addresses); Assert.Contains(SentinelServerC.EndPoint, addresses); @@ -452,6 +472,7 @@ public async Task SentinelGetSentinelAddressesTest() [Fact] public async Task ReadOnlyConnectionReplicasTest() { + SkipOnWindowsRelease(); var replicas = SentinelServerA.SentinelGetReplicaAddresses(ServiceName); if (replicas.Length == 0) { diff --git a/tests/StackExchange.Redis.Tests/TestBase.cs b/tests/StackExchange.Redis.Tests/TestBase.cs index 4106fba98..5a5ffe691 100644 --- a/tests/StackExchange.Redis.Tests/TestBase.cs +++ b/tests/StackExchange.Redis.Tests/TestBase.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using StackExchange.Redis.Configuration; @@ -702,4 +703,10 @@ public ValueTask DisposeAsync() return default; } } + + [Conditional("RELEASE")] + protected void SkipOnWindowsRelease(string? message = null) // typically used for tests that are super brittle on the Windows CI + { + Assert.SkipWhen(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), message ?? "skipping on Windows"); + } }