Skip to content

Commit d8aa7f8

Browse files
authored
LuaScript: use invariant regex on parameter parsing (fixes #2350) (#2351)
This changes the regex to use `RegexOptions.CultureInvariant` and adds an easy way to do tests like this in the future with `[Fact, TestCulture("tr-TR")]` for example, which will set and restore the culture for a specific test. Before/after break/fix test included for the Turkish case.
1 parent 757169b commit d8aa7f8

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Current package versions:
88

99
## Unreleased
1010

11-
No pending changes for the next release yet.
11+
- Fix [#2350](https://github.com/StackExchange/StackExchange.Redis/issues/2350): Properly parse lua script paramters in all cultures ([#2351 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2351))
1212

1313
## 2.6.90
1414

src/StackExchange.Redis/ScriptParameterMapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public ScriptParameters(RedisKey[] keys, RedisValue[] args)
2424
}
2525
}
2626

27-
private static readonly Regex ParameterExtractor = new Regex(@"@(?<paramName> ([a-z]|_) ([a-z]|_|\d)*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
27+
private static readonly Regex ParameterExtractor = new Regex(@"@(?<paramName> ([a-z]|_) ([a-z]|_|\d)*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant);
2828

2929
private static string[] ExtractParameters(string script)
3030
{

tests/StackExchange.Redis.Tests/Helpers/Attributes.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
34
using System.Linq;
5+
using System.Reflection;
46
using System.Threading;
57
using System.Threading.Tasks;
68
using Xunit.Abstractions;
@@ -184,3 +186,48 @@ public static RunSummary Update(this RunSummary summary, SkippableMessageBus bus
184186
return summary;
185187
}
186188
}
189+
190+
/// <summary>
191+
/// Supports changing culture for the duration of a single test.
192+
/// <see cref="Thread.CurrentThread" /> and <see cref="CultureInfo.CurrentCulture" /> with another culture.
193+
/// </summary>
194+
/// <remarks>
195+
/// Based on: https://bartwullems.blogspot.com/2022/03/xunit-change-culture-during-your-test.html
196+
/// </remarks>
197+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
198+
public class TestCultureAttribute : BeforeAfterTestAttribute
199+
{
200+
private readonly CultureInfo culture;
201+
private CultureInfo? originalCulture;
202+
203+
/// <summary>
204+
/// Replaces the culture and UI culture of the current thread with <paramref name="culture" />.
205+
/// </summary>
206+
/// <param name="culture">The name of the culture.</param>
207+
public TestCultureAttribute(string culture) => this.culture = new CultureInfo(culture, false);
208+
209+
/// <summary>
210+
/// Stores the current <see cref="Thread.CurrentPrincipal" /> and <see cref="CultureInfo.CurrentCulture" />
211+
/// and replaces them with the new cultures defined in the constructor.
212+
/// </summary>
213+
/// <param name="methodUnderTest">The method under test</param>
214+
public override void Before(MethodInfo methodUnderTest)
215+
{
216+
originalCulture = Thread.CurrentThread.CurrentCulture;
217+
Thread.CurrentThread.CurrentCulture = culture;
218+
CultureInfo.CurrentCulture.ClearCachedData();
219+
}
220+
221+
/// <summary>
222+
/// Restores the original <see cref="CultureInfo.CurrentCulture" /> to <see cref="Thread.CurrentPrincipal" />.
223+
/// </summary>
224+
/// <param name="methodUnderTest">The method under test</param>
225+
public override void After(MethodInfo methodUnderTest)
226+
{
227+
if (originalCulture is not null)
228+
{
229+
Thread.CurrentThread.CurrentCulture = originalCulture;
230+
CultureInfo.CurrentCulture.ClearCachedData();
231+
}
232+
}
233+
}

tests/StackExchange.Redis.Tests/ScriptingTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,29 @@ public async Task TestEvalShaReadOnlyAsync()
11131113
Assert.Equal("bar", result.ToString());
11141114
}
11151115

1116+
[Fact, TestCulture("en-US")]
1117+
public void LuaScriptEnglishParameters() => LuaScriptParameterShared();
1118+
1119+
[Fact, TestCulture("tr-TR")]
1120+
public void LuaScriptTurkishParameters() => LuaScriptParameterShared();
1121+
1122+
private void LuaScriptParameterShared()
1123+
{
1124+
const string Script = "redis.call('set', @key, @testIId)";
1125+
var prepared = LuaScript.Prepare(Script);
1126+
var key = Me();
1127+
var p = new { key = (RedisKey)key, testIId = "hello" };
1128+
1129+
prepared.ExtractParameters(p, null, out RedisKey[]? keys, out RedisValue[]? args);
1130+
Assert.NotNull(keys);
1131+
Assert.Single(keys);
1132+
Assert.Equal(key, keys[0]);
1133+
Assert.NotNull(args);
1134+
Assert.Equal(2, args.Length);
1135+
Assert.Equal(key, args[0]);
1136+
Assert.Equal("hello", args[1]);
1137+
}
1138+
11161139
private static void TestNullValue(RedisResult? value)
11171140
{
11181141
Assert.True(value == null || value.IsNull);

0 commit comments

Comments
 (0)