diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 12fe2741..6c107919 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,7 +37,7 @@ jobs: max-parallel: 15 fail-fast: false matrix: - redis-version: [ '8.0-M05-pre', '${{ needs.redis_version.outputs.CURRENT }}', '7.2.6', '6.2.16'] + redis-version: [ '8.0-RC1-pre', '${{ needs.redis_version.outputs.CURRENT }}', '7.2.6', '6.2.16'] dotnet-version: ['6.0', '7.0', '8.0'] env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true diff --git a/tests/NRedisStack.Tests/Search/SearchTests.cs b/tests/NRedisStack.Tests/Search/SearchTests.cs index fc77a3e4..9c059e2f 100644 --- a/tests/NRedisStack.Tests/Search/SearchTests.cs +++ b/tests/NRedisStack.Tests/Search/SearchTests.cs @@ -9,7 +9,6 @@ using NetTopologySuite.IO; using NetTopologySuite.Geometries; - namespace NRedisStack.Tests.Search; public class SearchTests : AbstractNRedisStackTest, IDisposable @@ -1384,7 +1383,7 @@ public void TestDropIndex(string endpointId) } catch (RedisServerException ex) { - Assert.Contains("no such index", ex.Message); + Assert.Contains("no such index", ex.Message, StringComparison.OrdinalIgnoreCase); } Assert.Equal("100", db.Execute("DBSIZE").ToString()); } @@ -1417,7 +1416,7 @@ public async Task TestDropIndexAsync(string endpointId) } catch (RedisServerException ex) { - Assert.Contains("no such index", ex.Message); + Assert.Contains("no such index", ex.Message, StringComparison.OrdinalIgnoreCase); } Assert.Equal("100", db.Execute("DBSIZE").ToString()); } @@ -3416,6 +3415,13 @@ public void TestNumericLogicalOperatorsInDialect4(string endpointId) Assert.Equal(1, ft.Search(index, new Query("@version==123 @id==456").Dialect(4)).TotalResults); } + /// + /// this test is to check if the issue 352 is fixed + /// Load operation was failing because the document was not being dropped in search result due to this behaviour; + /// "If a relevant key expires while a query is running, an attempt to load the key's value will return a null array. + /// However, the key is still counted in the total number of results." + /// https://redis.io/docs/latest/commands/ft.search/#:~:text=If%20a%20relevant%20key%20expires,the%20total%20number%20of%20results. + /// [Fact] public void TestDocumentLoad_Issue352() { @@ -3423,45 +3429,75 @@ public void TestDocumentLoad_Issue352() Assert.Empty(d.GetProperties().ToList()); } + /// + /// this test is to check if the issue 352 is fixed + /// Load operation was failing because the document was not being dropped in search result due to this behaviour; + /// "If a relevant key expires while a query is running, an attempt to load the key's value will return a null array. + /// However, the key is still counted in the total number of results." + /// https://redis.io/docs/latest/commands/ft.search/#:~:text=If%20a%20relevant%20key%20expires,the%20total%20number%20of%20results. + /// [SkippableTheory] [MemberData(nameof(EndpointsFixture.Env.StandaloneOnly), MemberType = typeof(EndpointsFixture.Env))] - public void TestDocumentLoadWithDB_Issue352(string endpointId) + public async void TestDocumentLoadWithDB_Issue352(string endpointId) { IDatabase db = GetCleanDatabase(endpointId); var ft = db.FT(); - Schema sc = new Schema().AddTextField("first", 1.0).AddTextField("last", 1.0).AddNumericField("age"); + Schema sc = new Schema().AddTextField("firstText", 1.0).AddTextField("lastText", 1.0).AddNumericField("ageNumeric"); Assert.True(ft.Create(index, FTCreateParams.CreateParams(), sc)); Document droppedDocument = null; int numberOfAttempts = 0; do { - db.HashSet("student:1111", new HashEntry[] { new("first", "Joe"), new("last", "Dod"), new("age", 18) }); + // try until succesfully create the key and set the TTL + bool ttlRefreshed = false; + do + { + db.HashSet("student:22222", new HashEntry[] { new("firstText", "Joe"), new("lastText", "Dod"), new("ageNumeric", 18) }); + ttlRefreshed = db.KeyExpire("student:22222", TimeSpan.FromMilliseconds(500)); + } while (!ttlRefreshed); - Assert.True(db.KeyExpire("student:1111", TimeSpan.FromMilliseconds(500))); + Int32 completed = 0; - Boolean cancelled = false; - Task searchTask = Task.Run(() => + Action checker = () => { - for (int i = 0; i < 100000; i++) + for (int i = 0; i < 1000000; i++) { SearchResult result = ft.Search(index, new Query()); List docs = result.Documents; - if (docs.Count == 0 || cancelled) + + // check if doc is already dropped before search and load; + // if yes then its already late and we missed the window that + // doc would show up in search result with no fields + if (docs.Count == 0) { + Interlocked.Increment(ref completed); break; } - else if (docs[0].GetProperties().ToList().Count == 0) + // if we get a document with no fields then we know that the key + // is going to be expired while the query is running, and we are able to catch the state + // but key itself might not be expired yet + else if (docs[0].GetProperties().Count() == 0) { droppedDocument = docs[0]; } } - }); - Task.WhenAny(searchTask, Task.Delay(1000)).GetAwaiter().GetResult(); - Assert.True(searchTask.IsCompleted); - Assert.Null(searchTask.Exception); - cancelled = true; - } while (droppedDocument == null && numberOfAttempts++ < 3); + }; + + List tasks = new List(); + // try with 3 different tasks simultaneously to increase the chance of hitting it + for (int i = 0; i < 3; i++) { tasks.Add(Task.Run(checker)); } + Task checkTask = Task.WhenAll(tasks); + await Task.WhenAny(checkTask, Task.Delay(1000)); + var keyTtl = db.KeyTimeToLive("student:22222"); + Assert.Equal(0, keyTtl.HasValue ? keyTtl.Value.Milliseconds : 0); + Assert.Equal(3, completed); + } while (droppedDocument == null && numberOfAttempts++ < 5); + // we won't do an actual assert here since + // it is not guaranteed that window stays open wide enough to catch it. + // instead we attempt 5 times. + // Without fix for Issue352, document load in this case fails %100 with my local test runs,, and %100 success with fixed version. + // The results in pipeline should be the same. } } diff --git a/tests/NRedisStack.Tests/TimeSeries/TestDataTypes/TestTimeSeriesInformation.cs b/tests/NRedisStack.Tests/TimeSeries/TestDataTypes/TestTimeSeriesInformation.cs index 6a8dd707..4f974123 100644 --- a/tests/NRedisStack.Tests/TimeSeries/TestDataTypes/TestTimeSeriesInformation.cs +++ b/tests/NRedisStack.Tests/TimeSeries/TestDataTypes/TestTimeSeriesInformation.cs @@ -27,14 +27,12 @@ public void TestInformationSync() TimeSeriesInformation info = ts.Info(key); TimeSeriesInformation infoDebug = ts.Info(key, debug: true); - Assert.Equal(4184, info.MemoryUsage); Assert.Equal(0, info.RetentionTime); Assert.Equal(1, info.ChunkCount); Assert.Null(info.DuplicatePolicy); Assert.Null(info.KeySelfName); Assert.Null(info.Chunks); - Assert.Equal(4184, infoDebug.MemoryUsage); Assert.Equal(0, infoDebug.RetentionTime); Assert.Equal(1, infoDebug.ChunkCount); Assert.Null(infoDebug.DuplicatePolicy); @@ -55,14 +53,12 @@ public async Task TestInformationAsync() TimeSeriesInformation info = await ts.InfoAsync(key); TimeSeriesInformation infoDebug = await ts.InfoAsync(key, debug: true); - Assert.Equal(4184, info.MemoryUsage); Assert.Equal(0, info.RetentionTime); Assert.Equal(1, info.ChunkCount); Assert.Null(info.DuplicatePolicy); Assert.Null(info.KeySelfName); Assert.Null(info.Chunks); - Assert.Equal(4184, infoDebug.MemoryUsage); Assert.Equal(0, infoDebug.RetentionTime); Assert.Equal(1, infoDebug.ChunkCount); Assert.Null(infoDebug.DuplicatePolicy);