From 7ed1a5d9b3e342a459febd04a786a2bfd682a04d Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 19 May 2026 17:07:54 +0100 Subject: [PATCH 1/3] default RESP3 everywhere --- .../AzureManagedRedisOptionsProvider.cs | 3 --- .../Configuration/DefaultOptionsProvider.cs | 2 +- .../PublicAPI/PublicAPI.Shipped.txt | 1 - tests/StackExchange.Redis.Tests/ConfigTests.cs | 11 +++++++---- .../StackExchange.Redis.Tests/RespProtocolTests.cs | 14 +++++++------- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs index 42bb2fcbf..60b8dce72 100644 --- a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs @@ -60,9 +60,6 @@ public override Task AfterConnectAsync(ConnectionMultiplexer muxer, Action public override bool GetDefaultSsl(EndPointCollection endPoints) => true; - /// - public override RedisProtocol? Protocol => RedisProtocol.Resp3; // prefer RESP3 on AMR - /// public override string ConfigurationChannel => ""; // disable on AMR } diff --git a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs index f560c8ce4..84d6399be 100644 --- a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs @@ -262,7 +262,7 @@ protected virtual string GetDefaultClientName() => /// /// Gets the preferred protocol to use for the connection. /// - public virtual RedisProtocol? Protocol => null; + public virtual RedisProtocol? Protocol => RedisProtocol.Resp3; /// /// Tries to get the RoleInstance Id if Microsoft.WindowsAzure.ServiceRuntime is loaded. diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 6df19ab81..860d2185c 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -2353,5 +2353,4 @@ StackExchange.Redis.KeyNotificationType.HExpire = 50 -> StackExchange.Redis.KeyN StackExchange.Redis.IDatabase.SortedSetIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags) -> double? StackExchange.Redis.IDatabaseAsync.SortedSetIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.ConfigurationChannel.get -> string! -override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.Protocol.get -> StackExchange.Redis.RedisProtocol? virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.Protocol.get -> StackExchange.Redis.RedisProtocol? diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 1e9f791f5..1a78fbf3b 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -175,11 +175,11 @@ public void ConfigurationOptionsDefaultForAzureManagedRedis(string hostAndPort, [InlineData("contoso.redis.azure.net:10000,protocol=resp2", RedisProtocol.Resp2, false)] // opt-out [InlineData("contoso.redis.azure.net:10000,protocol=resp3", RedisProtocol.Resp3, true)] // opt-in // azure redis cache, no overrides (we expect this to change in v3) - [InlineData("contoso.redis.cache.windows.net:6380", null, false)] // default + [InlineData("contoso.redis.cache.windows.net:6380", RedisProtocol.Resp3, true)] // default [InlineData("contoso.redis.cache.windows.net:6380,protocol=resp2", RedisProtocol.Resp2, false)] // opt-out [InlineData("contoso.redis.cache.windows.net:6380,protocol=resp3", RedisProtocol.Resp3, true)] // opt-in // arbitrary endpoint (we expect this to change in v3) - [InlineData("myserver:6379", null, false)] // default + [InlineData("myserver:6379", RedisProtocol.Resp3, true)] // default [InlineData("myserver:6379,protocol=resp2", RedisProtocol.Resp2, false)] // opt-out [InlineData("myserver:6379,protocol=resp3", RedisProtocol.Resp3, true)] // opt-in public void CorrectRespProtocol(string config, RedisProtocol? expected, bool useResp3) @@ -664,7 +664,7 @@ public async Task BeforeSocketConnect() }; await using var conn = ConnectionMultiplexer.Connect(options); Assert.True(conn.IsConnected); - Assert.Equal(2, count); + Assert.Equal(options.Protocol is RedisProtocol.Resp3 ? 1 : 2, count); var endpoint = conn.GetServerSnapshot()[0]; var interactivePhysical = endpoint.GetBridge(ConnectionType.Interactive)?.TryConnect(null); @@ -678,7 +678,10 @@ public async Task BeforeSocketConnect() Assert.NotNull(subscriptionSocket); Assert.Equal(12, interactiveSocket.Ttl); - Assert.Equal(123, subscriptionSocket.Ttl); + if (!ReferenceEquals(interactiveSocket, subscriptionSocket)) + { + Assert.Equal(123, subscriptionSocket.Ttl); + } Assert.True(interactiveSocket.DontFragment); Assert.True(subscriptionSocket.DontFragment); } diff --git a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs index 3e469918e..f48a903ba 100644 --- a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs +++ b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs @@ -18,7 +18,7 @@ public async Task ConnectWithTiming() [Theory] // specify nothing - [InlineData("someserver", false)] + [InlineData("someserver", true)] // specify *just* the protocol; sure, we'll believe you [InlineData("someserver,protocol=resp3", true)] [InlineData("someserver,protocol=resp3,$HELLO=", false)] @@ -30,9 +30,9 @@ public async Task ConnectWithTiming() [InlineData("someserver,protocol=2,$HELLO=", false, "resp2")] [InlineData("someserver,protocol=2,$HELLO=BONJOUR", false, "resp2")] // specify a pre-6 version - only used if protocol specified - [InlineData("someserver,version=5.9", false)] + [InlineData("someserver,version=5.9", true)] [InlineData("someserver,version=5.9,$HELLO=", false)] - [InlineData("someserver,version=5.9,$HELLO=BONJOUR", false)] + [InlineData("someserver,version=5.9,$HELLO=BONJOUR", true)] [InlineData("someserver,version=5.9,protocol=resp3", true)] [InlineData("someserver,version=5.9,protocol=resp3,$HELLO=", false)] [InlineData("someserver,version=5.9,protocol=resp3,$HELLO=BONJOUR", true)] @@ -43,9 +43,9 @@ public async Task ConnectWithTiming() [InlineData("someserver,version=5.9,protocol=2,$HELLO=", false, "resp2")] [InlineData("someserver,version=5.9,protocol=2,$HELLO=BONJOUR", false, "resp2")] // specify a post-6 version; attempt by default - [InlineData("someserver,version=6.0", false)] + [InlineData("someserver,version=6.0", true)] [InlineData("someserver,version=6.0,$HELLO=", false)] - [InlineData("someserver,version=6.0,$HELLO=BONJOUR", false)] + [InlineData("someserver,version=6.0,$HELLO=BONJOUR", true)] [InlineData("someserver,version=6.0,protocol=resp3", true)] [InlineData("someserver,version=6.0,protocol=resp3,$HELLO=", false)] [InlineData("someserver,version=6.0,protocol=resp3,$HELLO=BONJOUR", true)] @@ -55,9 +55,9 @@ public async Task ConnectWithTiming() [InlineData("someserver,version=6.0,protocol=2", false, "resp2")] [InlineData("someserver,version=6.0,protocol=2,$HELLO=", false, "resp2")] [InlineData("someserver,version=6.0,protocol=2,$HELLO=BONJOUR", false, "resp2")] - [InlineData("someserver,version=7.2", false)] + [InlineData("someserver,version=7.2", true)] [InlineData("someserver,version=7.2,$HELLO=", false)] - [InlineData("someserver,version=7.2,$HELLO=BONJOUR", false)] + [InlineData("someserver,version=7.2,$HELLO=BONJOUR", true)] public void ParseFormatConfigOptions(string configurationString, bool tryResp3, string? formatProtocol = null) { var config = ConfigurationOptions.Parse(configurationString); From a94cb0284a17801fa712caf1affaf6b8beae2165 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 20 May 2026 14:10:05 +0100 Subject: [PATCH 2/3] Retain public override bool GetDefaultSsl(EndPointCollection endPoints) => true; + /// + public override RedisProtocol? Protocol => RedisProtocol.Resp3; // prefer RESP3 on AMR + /// public override string ConfigurationChannel => ""; // disable on AMR } diff --git a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs index 84d6399be..613a092b4 100644 --- a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs @@ -140,7 +140,10 @@ public static DefaultOptionsProvider GetProvider(EndPoint endpoint) /// /// The server version to assume. /// - public virtual Version DefaultVersion => RedisFeatures.v3_0_0; + public virtual Version DefaultVersion => BaseDefaultVersion; + + // this exists primarily to be queryable from tests + internal static Version BaseDefaultVersion = RedisFeatures.v6_0_0; /// /// Controls how often the connection heartbeats. A heartbeat includes: @@ -262,7 +265,7 @@ protected virtual string GetDefaultClientName() => /// /// Gets the preferred protocol to use for the connection. /// - public virtual RedisProtocol? Protocol => RedisProtocol.Resp3; + public virtual RedisProtocol? Protocol => null; /// /// Tries to get the RoleInstance Id if Microsoft.WindowsAzure.ServiceRuntime is loaded. diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index a3da692ef..31df3cc42 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -1194,23 +1194,13 @@ public RedisProtocol? Protocol internal bool TryResp3() { + // if Protocol specified: fine, otherwise lean on the server version var protocol = Protocol; - // note: deliberately leaving the IsAvailable duplicated to use short-circuit - - // if (protocol is null) - // { - // // if not specified, lean on the server version and whether HELLO is available - // return new RedisFeatures(DefaultVersion).Resp3 && CommandMap.IsAvailable(RedisCommand.HELLO); - // } - // else - // ^^^ left for context; originally our intention was to auto-enable RESP3 by default *if* the server version - // is >= 6; however, it turns out (see extensive conversation here https://github.com/StackExchange/StackExchange.Redis/pull/2396) - // that tangential undocumented API breaks were made at the same time; this means that even if we fix every - // edge case in the library itself, the break is still visible to external callers via Execute[Async]; with an - // abundance of caution, we are therefore making RESP3 explicit opt-in only for now; we may revisit this in a major - { - return protocol.GetValueOrDefault() >= RedisProtocol.Resp3 && CommandMap.IsAvailable(RedisCommand.HELLO); - } + bool use3 = protocol is null + ? new RedisFeatures(DefaultVersion).Resp3 + : protocol.GetValueOrDefault() >= RedisProtocol.Resp3; + // either way, it requires HELLO + return use3 && CommandMap.IsAvailable(RedisCommand.HELLO); } internal static bool TryParseRedisProtocol(string? value, out RedisProtocol protocol) diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index c985cfb7d..5319c6f00 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ #nullable enable +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.Protocol.get -> StackExchange.Redis.RedisProtocol? [SER005]StackExchange.Redis.TestHarness [SER005]StackExchange.Redis.TestHarness.BufferValidator [SER005]StackExchange.Redis.TestHarness.ChannelPrefix.get -> StackExchange.Redis.RedisChannel diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 1a78fbf3b..55dcc5ce7 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Runtime.CompilerServices; using System.Security.Authentication; using System.Text; using System.Text.RegularExpressions; @@ -13,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using StackExchange.Redis.Configuration; +using StackExchange.Redis.Tests.Helpers; using Xunit; namespace StackExchange.Redis.Tests; @@ -20,7 +22,31 @@ namespace StackExchange.Redis.Tests; [RunPerProtocol] public class ConfigTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) { - public Version DefaultVersion = new(3, 0, 0); + private static Version BaseDefaultVersion => DefaultOptionsProvider.BaseDefaultVersion; + + private static void ApplyTestDefaults(ConfigurationOptions options, bool applyProtocol = true) + { + if (applyProtocol) options.Protocol = TestContext.Current.GetProtocol(); + } + + private static string RemoveTestDefaults(string configurationString) + { + var pattern = TestContext.Current.GetProtocol() switch + { + RedisProtocol.Resp2 => ",protocol=resp2(?=,|$)", + RedisProtocol.Resp3 => ",protocol=resp3(?=,|$)", + _ => null, + }; + return pattern is null + ? configurationString + : Regex.Replace(configurationString, pattern, ""); + } + private static ConfigurationOptions Parse(string configuration, bool applyProtocol = true) + { + var options = ConfigurationOptions.Parse(configuration); + ApplyTestDefaults(options, applyProtocol); + return options; + } [Fact] public void ExpectedFields() @@ -92,14 +118,14 @@ orderby name [Fact] public void SslProtocols_SingleValue() { - var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls12"); + var options = Parse("myhost,sslProtocols=Tls12"); Assert.Equal(SslProtocols.Tls12, options.SslProtocols.GetValueOrDefault()); } [Fact] public void SslProtocols_MultipleValues() { - var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls12|Tls13"); + var options = Parse("myhost,sslProtocols=Tls12|Tls13"); Assert.Equal(SslProtocols.Tls12 | SslProtocols.Tls13, options.SslProtocols.GetValueOrDefault()); } @@ -109,7 +135,7 @@ public void SslProtocols_MultipleValues() [InlineData("", true)] public void ConfigurationOption_CheckCertificateRevocation(string conString, bool expectedValue) { - var options = ConfigurationOptions.Parse($"host,{conString}"); + var options = Parse($"host,{conString}"); Assert.Equal(expectedValue, options.CheckCertificateRevocation); var toString = options.ToString(); Assert.Contains(conString, toString, StringComparison.CurrentCultureIgnoreCase); @@ -122,14 +148,14 @@ public void SslProtocols_UsingIntegerValue() // .NET framework version (e.g. .NET 4.0) doesn't define an enum value (e.g. Tls11) // but the OS has been patched with support const int integerValue = (int)(SslProtocols.Tls12 | SslProtocols.Tls13); - var options = ConfigurationOptions.Parse("myhost,sslProtocols=" + integerValue); + var options = Parse("myhost,sslProtocols=" + integerValue); Assert.Equal(SslProtocols.Tls12 | SslProtocols.Tls13, options.SslProtocols.GetValueOrDefault()); } [Fact] public void SslProtocols_InvalidValue() { - Assert.Throws(() => ConfigurationOptions.Parse("myhost,sslProtocols=InvalidSslProtocol")); + Assert.Throws(() => Parse("myhost,sslProtocols=InvalidSslProtocol")); } [Theory] @@ -144,7 +170,7 @@ public void SslProtocols_InvalidValue() public void ConfigurationOptionsDefaultForAzure(string hostAndPort, bool sslShouldBeEnabled) { Version defaultAzureVersion = new(6, 0, 0); - var options = ConfigurationOptions.Parse(hostAndPort); + var options = Parse(hostAndPort); Assert.True(options.DefaultVersion.Equals(defaultAzureVersion)); Assert.False(options.AbortOnConnectFail); Assert.Equal(sslShouldBeEnabled, options.Ssl); @@ -163,7 +189,7 @@ public void ConfigurationOptionsDefaultForAzure(string hostAndPort, bool sslShou public void ConfigurationOptionsDefaultForAzureManagedRedis(string hostAndPort, bool sslShouldBeEnabled) { Version defaultAzureManagedRedisVersion = new(7, 4, 0); - var options = ConfigurationOptions.Parse(hostAndPort); + var options = Parse(hostAndPort); Assert.True(options.DefaultVersion.Equals(defaultAzureManagedRedisVersion)); Assert.False(options.AbortOnConnectFail); Assert.Equal(sslShouldBeEnabled, options.Ssl); @@ -174,17 +200,20 @@ public void ConfigurationOptionsDefaultForAzureManagedRedis(string hostAndPort, [InlineData("contoso.redis.azure.net:10000", RedisProtocol.Resp3, true)] // default [InlineData("contoso.redis.azure.net:10000,protocol=resp2", RedisProtocol.Resp2, false)] // opt-out [InlineData("contoso.redis.azure.net:10000,protocol=resp3", RedisProtocol.Resp3, true)] // opt-in + [InlineData("contoso.redis.azure.net:10000,version=5", RedisProtocol.Resp3, true)] // low version *ignored* (provider wins) // azure redis cache, no overrides (we expect this to change in v3) - [InlineData("contoso.redis.cache.windows.net:6380", RedisProtocol.Resp3, true)] // default + [InlineData("contoso.redis.cache.windows.net:6380", null, true)] // default [InlineData("contoso.redis.cache.windows.net:6380,protocol=resp2", RedisProtocol.Resp2, false)] // opt-out [InlineData("contoso.redis.cache.windows.net:6380,protocol=resp3", RedisProtocol.Resp3, true)] // opt-in + [InlineData("contoso.redis.cache.windows.net:6380,version=5", null, false)] // low version means resp2 // arbitrary endpoint (we expect this to change in v3) - [InlineData("myserver:6379", RedisProtocol.Resp3, true)] // default + [InlineData("myserver:6379", null, true)] // default [InlineData("myserver:6379,protocol=resp2", RedisProtocol.Resp2, false)] // opt-out [InlineData("myserver:6379,protocol=resp3", RedisProtocol.Resp3, true)] // opt-in + [InlineData("myserver:6379,version=5", null, false)] // low version means resp2 public void CorrectRespProtocol(string config, RedisProtocol? expected, bool useResp3) { - var options = ConfigurationOptions.Parse(config); + var options = Parse(config, applyProtocol: false); Assert.Equal(expected, options.Protocol); Assert.Equal(useResp3, options.TryResp3()); } @@ -192,7 +221,7 @@ public void CorrectRespProtocol(string config, RedisProtocol? expected, bool use [Fact] public void ConfigurationOptionsForAzureWhenSpecified() { - var options = ConfigurationOptions.Parse("contoso.redis.cache.windows.net,abortConnect=true, version=2.1.1"); + var options = Parse("contoso.redis.cache.windows.net,abortConnect=true, version=2.1.1"); Assert.True(options.DefaultVersion.Equals(new Version(2, 1, 1))); Assert.True(options.AbortOnConnectFail); } @@ -213,8 +242,8 @@ public void ConfigurationOptionsForAzureWhenSpecified() [InlineData("contoso.redis.azure.net:")] // AMR host name with missing port public void ConfigurationOptionsDefaultForNonAzure(string hostAndPort) { - var options = ConfigurationOptions.Parse(hostAndPort); - Assert.True(options.DefaultVersion.Equals(DefaultVersion)); + var options = Parse(hostAndPort); + Assert.True(options.DefaultVersion.Equals(BaseDefaultVersion)); Assert.True(options.AbortOnConnectFail); Assert.False(options.Ssl); } @@ -223,7 +252,7 @@ public void ConfigurationOptionsDefaultForNonAzure(string hostAndPort) public void ConfigurationOptionsDefaultWhenNoEndpointsSpecifiedYet() { var options = new ConfigurationOptions(); - Assert.True(options.DefaultVersion.Equals(DefaultVersion)); + Assert.True(options.DefaultVersion.Equals(BaseDefaultVersion)); Assert.True(options.AbortOnConnectFail); } @@ -234,7 +263,7 @@ public void ConfigurationOptionsSyncTimeout() var options = new ConfigurationOptions(); Assert.Equal(5000, options.SyncTimeout); - options = ConfigurationOptions.Parse("syncTimeout=20"); + options = Parse("syncTimeout=20"); Assert.Equal(20, options.SyncTimeout); } @@ -245,7 +274,7 @@ public void ConfigurationOptionsSyncTimeout() [InlineData("[2a01:9820:1:24::1:1]:6379", AddressFamily.InterNetworkV6, "2a01:9820:1:24::1:1", 6379)] public void ConfigurationOptionsIPv6Parsing(string configString, AddressFamily family, string address, int port) { - var options = ConfigurationOptions.Parse(configString); + var options = Parse(configString); Assert.Single(options.EndPoints); var ep = Assert.IsType(options.EndPoints[0]); Assert.Equal(family, ep.AddressFamily); @@ -258,14 +287,14 @@ public void CanParseAndFormatUnixDomainSocket() { const string ConfigString = "!/some/path,allowAdmin=True"; #if NETFRAMEWORK - var ex = Assert.Throws(() => ConfigurationOptions.Parse(ConfigString)); + var ex = Assert.Throws(() => Parse(ConfigString)); Assert.Equal("Unix domain sockets require .NET Core 3 or above", ex.Message); #else - var config = ConfigurationOptions.Parse(ConfigString); + var config = Parse(ConfigString); Assert.True(config.AllowAdmin); var ep = Assert.IsType(Assert.Single(config.EndPoints)); Assert.Equal("/some/path", ep.ToString()); - Assert.Equal(ConfigString, config.ToString()); + Assert.Equal(ConfigString, RemoveTestDefaults(config.ToString())); #endif } @@ -281,6 +310,7 @@ public async Task TalkToNonsenseServer() }, ConnectTimeout = 200, }; + ApplyTestDefaults(config); var log = new StringWriter(); await using (var conn = ConnectionMultiplexer.Connect(config, log)) { @@ -292,7 +322,7 @@ public async Task TalkToNonsenseServer() [Fact] public async Task TestManualHeartbeat() { - var options = ConfigurationOptions.Parse(GetConfiguration()); + var options = Parse(GetConfiguration()); options.HeartbeatInterval = TimeSpan.FromMilliseconds(100); await using var conn = await ConnectionMultiplexer.ConnectAsync(options); @@ -598,7 +628,7 @@ public void EndpointIteratorIsReliableOverChanges() [InlineData("myDNS:myPort,password=myPassword,abortConnect=false,ssl=true,sslProtocols=Tls12 ", SslProtocols.Tls12)] public void ParseTlsWithoutTrailingComma(string configString, SslProtocols expected) { - var config = ConfigurationOptions.Parse(configString); + var config = Parse(configString); Assert.Equal(expected, config.SslProtocols); } @@ -611,7 +641,7 @@ public void ParseTlsWithoutTrailingComma(string configString, SslProtocols expec [InlineData("foo,proxy=epoxy", "Keyword 'proxy' requires a proxy value; the value 'epoxy' is not recognised.", "proxy")] public void ConfigStringErrorsGiveMeaningfulMessages(string configString, string expected, string paramName) { - var ex = Assert.Throws(() => ConfigurationOptions.Parse(configString)); + var ex = Assert.Throws(() => Parse(configString)); Assert.StartsWith(expected, ex.Message); // param name gets concatenated sometimes Assert.Equal(paramName, ex.ParamName); // param name gets concatenated sometimes } @@ -619,7 +649,7 @@ public void ConfigStringErrorsGiveMeaningfulMessages(string configString, string [Fact] public void ConfigStringInvalidOptionErrorGiveMeaningfulMessages() { - var ex = Assert.Throws(() => ConfigurationOptions.Parse("foo,flibble=value")); + var ex = Assert.Throws(() => Parse("foo,flibble=value")); Assert.StartsWith("Keyword 'flibble' is not supported.", ex.Message); // param name gets concatenated sometimes Assert.Equal("flibble", ex.ParamName); } @@ -627,7 +657,7 @@ public void ConfigStringInvalidOptionErrorGiveMeaningfulMessages() [Fact] public void NullApply() { - var options = ConfigurationOptions.Parse("127.0.0.1,name=FooApply"); + var options = Parse("127.0.0.1,name=FooApply"); Assert.Equal("FooApply", options.ClientName); // Doesn't go boom @@ -639,7 +669,7 @@ public void NullApply() [Fact] public void Apply() { - var options = ConfigurationOptions.Parse("127.0.0.1,name=FooApply"); + var options = Parse("127.0.0.1,name=FooApply"); Assert.Equal("FooApply", options.ClientName); var randomName = Guid.NewGuid().ToString(); @@ -653,7 +683,7 @@ public void Apply() [Fact] public async Task BeforeSocketConnect() { - var options = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort); + var options = Parse(TestConfig.Current.PrimaryServerAndPort); int count = 0; options.BeforeSocketConnect = (endpoint, connType, socket) => { @@ -664,7 +694,7 @@ public async Task BeforeSocketConnect() }; await using var conn = ConnectionMultiplexer.Connect(options); Assert.True(conn.IsConnected); - Assert.Equal(options.Protocol is RedisProtocol.Resp3 ? 1 : 2, count); + Assert.Equal(options.TryResp3() ? 1 : 2, count); var endpoint = conn.GetServerSnapshot()[0]; var interactivePhysical = endpoint.GetBridge(ConnectionType.Interactive)?.TryConnect(null); @@ -689,13 +719,17 @@ public async Task BeforeSocketConnect() [Fact] public async Task MutableOptions() { - var options = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort + ",name=Details"); + var options = Parse(TestConfig.Current.PrimaryServerAndPort + ",name=Details"); options.LoggerFactory = NullLoggerFactory.Instance; var originalConfigChannel = options.ConfigurationChannel = "originalConfig"; var originalUser = options.User = "originalUser"; var originalPassword = options.Password = "originalPassword"; Assert.Equal("Details", options.ClientName); - await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + Assert.SkipWhen(options.TryResp3(), "only validate RESP2"); + Log(options.ToString()); + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, log: Writer); + Assert.NotNull(conn.AuthException); + Log($"auth failure: {conn.AuthException.Message}"); // Same instance Assert.Same(options, conn.RawConfig); @@ -740,6 +774,7 @@ public async Task MutableOptions() var newPass = options.Password = "newPassword"; Assert.Equal(newPass, conn.RawConfig.Password); Assert.Equal(options.LoggerFactory, conn.RawConfig.LoggerFactory); + Log("complete"); } [Theory] @@ -747,7 +782,7 @@ public async Task MutableOptions() [InlineData("http:somewhere:22", "http:somewhere:22")] public void HttpTunnelCanRoundtrip(string input, string expected) { - var config = ConfigurationOptions.Parse($"127.0.0.1:6380,tunnel={input}"); + var config = Parse($"127.0.0.1:6380,tunnel={input}"); var ip = Assert.IsType(Assert.Single(config.EndPoints)); Assert.Equal(6380, ip.Port); Assert.Equal("127.0.0.1", ip.Address.ToString()); @@ -756,7 +791,7 @@ public void HttpTunnelCanRoundtrip(string input, string expected) Assert.Equal(expected, config.Tunnel.ToString()); var cs = config.ToString(); - Assert.Equal($"127.0.0.1:6380,tunnel={expected}", cs); + Assert.Equal($"127.0.0.1:6380,tunnel={expected}", RemoveTestDefaults(cs)); } private sealed class CustomTunnel : Tunnel { } @@ -766,11 +801,11 @@ public void CustomTunnelCanRoundtripMinusTunnel() { // we don't expect to be able to parse custom tunnels, but we should still be able to round-trip // the rest of the config, which means ignoring them *in both directions* (unless first party) - var options = ConfigurationOptions.Parse("127.0.0.1,Ssl=true"); + var options = Parse("127.0.0.1,Ssl=true"); options.Tunnel = new CustomTunnel(); var cs = options.ToString(); - Assert.Equal("127.0.0.1,ssl=True", cs); - options = ConfigurationOptions.Parse(cs); + Assert.Equal("127.0.0.1,ssl=True", RemoveTestDefaults(cs)); + options = Parse(cs); Assert.Null(options.Tunnel); } @@ -780,12 +815,12 @@ public void CustomTunnelCanRoundtripMinusTunnel() [InlineData("server:6379,setlib=False", false)] public void DefaultConfigOptionsForSetLib(string configurationString, bool setlib) { - var options = ConfigurationOptions.Parse(configurationString); + var options = Parse(configurationString); Assert.Equal(setlib, options.SetClientLibrary); - Assert.Equal(configurationString, options.ToString()); + Assert.Equal(configurationString, RemoveTestDefaults(options.ToString())); options = options.Clone(); Assert.Equal(setlib, options.SetClientLibrary); - Assert.Equal(configurationString, options.ToString()); + Assert.Equal(configurationString, RemoveTestDefaults(options.ToString())); } [Theory] @@ -794,17 +829,17 @@ public void DefaultConfigOptionsForSetLib(string configurationString, bool setli [InlineData(true, true, "dummy,highIntegrity=True")] public void CheckHighIntegrity(bool? assigned, bool expected, string cs) { - var options = ConfigurationOptions.Parse("dummy"); + var options = Parse("dummy"); if (assigned.HasValue) options.HighIntegrity = assigned.Value; Assert.Equal(expected, options.HighIntegrity); - Assert.Equal(cs, options.ToString()); + Assert.Equal(cs, RemoveTestDefaults(options.ToString())); var clone = options.Clone(); Assert.Equal(expected, clone.HighIntegrity); - Assert.Equal(cs, clone.ToString()); + Assert.Equal(cs, RemoveTestDefaults(clone.ToString())); - var parsed = ConfigurationOptions.Parse(cs); + var parsed = Parse(cs); Assert.Equal(expected, parsed.HighIntegrity); } diff --git a/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs b/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs index a3386e80c..1f29a6b55 100644 --- a/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs +++ b/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs @@ -59,7 +59,7 @@ protected override ValueTask> CreateTestCase } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] -public class RunPerProtocol() : Attribute { } +public class RunPerProtocolAttribute() : Attribute { } public interface IProtocolTestCase { @@ -155,8 +155,8 @@ public static async ValueTask> ExpandAsync(t { var testMethod = testCase.TestMethod; - if ((testMethod.Method.GetCustomAttributes(typeof(RunPerProtocol)).FirstOrDefault() - ?? testMethod.TestClass.Class.GetCustomAttributes(typeof(RunPerProtocol)).FirstOrDefault()) is RunPerProtocol) + if ((testMethod.Method.GetCustomAttributes(typeof(RunPerProtocolAttribute)).FirstOrDefault() + ?? testMethod.TestClass.Class.GetCustomAttributes(typeof(RunPerProtocolAttribute)).FirstOrDefault()) is RunPerProtocolAttribute) { result.Add(CreateTestCase(testCase, RedisProtocol.Resp2)); result.Add(CreateTestCase(testCase, RedisProtocol.Resp3)); diff --git a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs index f48a903ba..69bd26dbd 100644 --- a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs +++ b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs @@ -30,9 +30,9 @@ public async Task ConnectWithTiming() [InlineData("someserver,protocol=2,$HELLO=", false, "resp2")] [InlineData("someserver,protocol=2,$HELLO=BONJOUR", false, "resp2")] // specify a pre-6 version - only used if protocol specified - [InlineData("someserver,version=5.9", true)] + [InlineData("someserver,version=5.9", false)] [InlineData("someserver,version=5.9,$HELLO=", false)] - [InlineData("someserver,version=5.9,$HELLO=BONJOUR", true)] + [InlineData("someserver,version=5.9,$HELLO=BONJOUR", false)] [InlineData("someserver,version=5.9,protocol=resp3", true)] [InlineData("someserver,version=5.9,protocol=resp3,$HELLO=", false)] [InlineData("someserver,version=5.9,protocol=resp3,$HELLO=BONJOUR", true)] From 9c1a2b2c28517698e6f07a1866408e4084e23dc2 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 20 May 2026 14:11:47 +0100 Subject: [PATCH 3/3] move ship file entries --- src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt | 1 + src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 860d2185c..6df19ab81 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -2353,4 +2353,5 @@ StackExchange.Redis.KeyNotificationType.HExpire = 50 -> StackExchange.Redis.KeyN StackExchange.Redis.IDatabase.SortedSetIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags) -> double? StackExchange.Redis.IDatabaseAsync.SortedSetIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.ConfigurationChannel.get -> string! +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.Protocol.get -> StackExchange.Redis.RedisProtocol? virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.Protocol.get -> StackExchange.Redis.RedisProtocol? diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 5319c6f00..c985cfb7d 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1,5 +1,4 @@ #nullable enable -override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.Protocol.get -> StackExchange.Redis.RedisProtocol? [SER005]StackExchange.Redis.TestHarness [SER005]StackExchange.Redis.TestHarness.BufferValidator [SER005]StackExchange.Redis.TestHarness.ChannelPrefix.get -> StackExchange.Redis.RedisChannel