From 3b7cd98a24b52136dfedde133b720e486aaf131f Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 22 Jan 2025 01:42:57 +0800 Subject: [PATCH 01/14] improve: scan operations for ReadOnlyStoreView --- .../Persistence/ReadOnlyViewExtensions.cs | 139 ++++++++++++++++++ src/Neo/Persistence/DataCache.cs | 71 ++------- src/Neo/Persistence/IReadOnlyStoreView.cs | 19 ++- src/Neo/Persistence/ReadOnlyStoreView.cs | 28 +++- .../Extensions/UT_ReadOnlyViewExtension.cs | 111 ++++++++++++++ 5 files changed, 301 insertions(+), 67 deletions(-) create mode 100644 src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs create mode 100644 tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs new file mode 100644 index 0000000000..02e7483b7c --- /dev/null +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -0,0 +1,139 @@ +// Copyright (C) 2015-2025 The Neo Project. +// +// ReadOnlyViewExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +#nullable enable + +using Neo.Persistence; +using Neo.SmartContract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Neo.Extensions +{ + public static class ReadOnlyViewExtensions + { + /// + /// Scans the entries starting with the specified prefix. + /// + /// The view to scan. + /// + /// The prefix of the key. + /// + /// If is , + /// the cannot be null or empty. + /// + /// If is , + /// it seeks to the first entry if is null or empty. + /// + /// If want to scan all entries with backward, + /// set to be n * 0xff, where n is the max length of the key. + /// + /// The search direction. + /// The entries found with the desired prefix. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable<(StorageKey Key, StorageItem Value)> ScanPrefix( + this IReadOnlyStoreView view, + byte[]? keyPrefix, + SeekDirection direction = SeekDirection.Forward) + { + var seekPrefix = direction == SeekDirection.Forward ? keyPrefix : keyPrefix.GetSeekPrefix(); + return view.ScanPrefix(keyPrefix, seekPrefix, direction); + } + + internal static IEnumerable<(StorageKey Key, StorageItem Value)> ScanPrefix( + this IReadOnlyStoreView view, + byte[]? keyPrefix, + byte[]? seekPrefix, + SeekDirection direction = SeekDirection.Forward) + { + foreach (var (key, value) in view.Seek(seekPrefix, direction)) + { + if (keyPrefix == null || key.ToArray().AsSpan().StartsWith(keyPrefix)) + yield return (key, value); + else if (direction == SeekDirection.Forward || (seekPrefix == null || !key.ToArray().SequenceEqual(seekPrefix))) + yield break; + } + } + + /// + /// Scans the entries in the specified range. + /// + /// The view to scan. + /// The inclusive start key. + /// The exclusive end key. + /// The search direction. + /// The entries found in the specified range. + public static IEnumerable<(StorageKey Key, StorageItem Value)> ScanRange( + this IReadOnlyStoreView view, + byte[]? inclusiveStartKey, + byte[] exclusiveEndKey, + SeekDirection direction = SeekDirection.Forward) + { + ByteArrayComparer comparer = direction == SeekDirection.Forward + ? ByteArrayComparer.Default + : ByteArrayComparer.Reverse; + foreach (var (key, value) in view.Seek(inclusiveStartKey, direction)) + { + if (comparer.Compare(key.ToArray(), exclusiveEndKey) < 0) + yield return (key, value); + else + yield break; + } + } + + /// + /// Gets the seek prefix for the specified key prefix. + /// + /// The key prefix. + /// + /// The maximum size when all bytes are 0xff. + /// If the key prefix is all 0xff, and > 0, + /// the seek prefix will be set to be byte[maxSizeWhenAll0xff] and filled with 0xff. + /// If is less than or equal to 0, an ArgumentException will be thrown. + /// + /// The seek prefix. + /// Thrown when is null. + /// Thrown when is empty. + /// + /// Thrown when is all 0xff and is less than or equal to 0. + /// + internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, int maxSizeWhenAll0xff = 4096 /* make it long enough */) + { + if (keyPrefix == null) // Backwards seek for null prefix is not supported for now. + throw new ArgumentNullException(nameof(keyPrefix)); + + if (keyPrefix.Length == 0) // Backwards seek for zero prefix is not supported for now. + throw new ArgumentOutOfRangeException(nameof(keyPrefix)); + + byte[]? seekPrefix = null; + for (var i = keyPrefix.Length - 1; i >= 0; i--) + { + if (keyPrefix[i] < 0xff) + { + seekPrefix = keyPrefix.Take(i + 1).ToArray(); + seekPrefix[i]++; // The next key after the key_prefix. + break; + } + } + + if (seekPrefix == null) + { + if (maxSizeWhenAll0xff > 0) + seekPrefix = new byte[maxSizeWhenAll0xff]; + else + throw new ArgumentException($"{nameof(keyPrefix)} with all bytes being 0xff is not supported now"); + } + return seekPrefix; + } + } +} diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index 016bed2ee2..4f3ade49eb 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -202,50 +202,14 @@ public void Delete(StorageKey key) /// /// Finds the entries starting with the specified prefix. /// - /// The prefix of the key. + /// The prefix of the key. /// The search direction. /// The entries found with the desired prefix. - public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[]? key_prefix = null, SeekDirection direction = SeekDirection.Forward) + public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[]? keyPrefix = null, SeekDirection direction = SeekDirection.Forward) { - var seek_prefix = key_prefix; - if (direction == SeekDirection.Backward) - { - if (key_prefix == null) - { - // Backwards seek for null prefix is not supported for now. - throw new ArgumentNullException(nameof(key_prefix)); - } - if (key_prefix.Length == 0) - { - // Backwards seek for zero prefix is not supported for now. - throw new ArgumentOutOfRangeException(nameof(key_prefix)); - } - seek_prefix = null; - for (var i = key_prefix.Length - 1; i >= 0; i--) - { - if (key_prefix[i] < 0xff) - { - seek_prefix = key_prefix.Take(i + 1).ToArray(); - // The next key after the key_prefix. - seek_prefix[i]++; - break; - } - } - if (seek_prefix == null) - { - throw new ArgumentException($"{nameof(key_prefix)} with all bytes being 0xff is not supported now"); - } - } - return FindInternal(key_prefix, seek_prefix, direction); - } - - private IEnumerable<(StorageKey Key, StorageItem Value)> FindInternal(byte[]? key_prefix, byte[]? seek_prefix, SeekDirection direction) - { - foreach (var (key, value) in Seek(seek_prefix, direction)) - if (key_prefix == null || key.ToArray().AsSpan().StartsWith(key_prefix)) - yield return (key, value); - else if (direction == SeekDirection.Forward || (seek_prefix == null || !key.ToArray().SequenceEqual(seek_prefix))) - yield break; + // GetSeekPrefix with 0 for compatibility with old code + var seekPrefix = direction == SeekDirection.Forward ? keyPrefix : keyPrefix.GetSeekPrefix(0); + return this.ScanPrefix(keyPrefix, seekPrefix, direction); } /// @@ -257,14 +221,7 @@ public void Delete(StorageKey key) /// The entries found with the desired range. public IEnumerable<(StorageKey Key, StorageItem Value)> FindRange(byte[] start, byte[] end, SeekDirection direction = SeekDirection.Forward) { - ByteArrayComparer comparer = direction == SeekDirection.Forward - ? ByteArrayComparer.Default - : ByteArrayComparer.Reverse; - foreach (var (key, value) in Seek(start, direction)) - if (comparer.Compare(key.ToArray(), end) < 0) - yield return (key, value); - else - yield break; + return this.ScanRange(start, end, direction); } /// @@ -420,24 +377,16 @@ public StorageItem GetOrAdd(StorageKey key, Func factory) { cached = _dictionary .Where(p => p.Value.State != TrackState.Deleted && p.Value.State != TrackState.NotFound && (keyOrPrefix == null || comparer.Compare(p.Key.ToArray(), keyOrPrefix) >= 0)) - .Select(p => - ( - KeyBytes: p.Key.ToArray(), - p.Key, - p.Value.Item - )) + .Select(p => (KeyBytes: p.Key.ToArray(), p.Key, p.Value.Item)) .OrderBy(p => p.KeyBytes, comparer) .ToArray(); cachedKeySet = new HashSet(_dictionary.Keys); } + var uncached = SeekInternal(keyOrPrefix ?? Array.Empty(), direction) .Where(p => !cachedKeySet.Contains(p.Key)) - .Select(p => - ( - KeyBytes: p.Key.ToArray(), - p.Key, - p.Value - )); + .Select(p => (KeyBytes: p.Key.ToArray(), p.Key, p.Value)); + using var e1 = cached.GetEnumerator(); using var e2 = uncached.GetEnumerator(); (byte[] KeyBytes, StorageKey Key, StorageItem Item) i1, i2; diff --git a/src/Neo/Persistence/IReadOnlyStoreView.cs b/src/Neo/Persistence/IReadOnlyStoreView.cs index b5d4d050e7..ac00fdc476 100644 --- a/src/Neo/Persistence/IReadOnlyStoreView.cs +++ b/src/Neo/Persistence/IReadOnlyStoreView.cs @@ -9,8 +9,11 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +#nullable enable + using Neo.SmartContract; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Neo.Persistence { @@ -41,6 +44,20 @@ public interface IReadOnlyStoreView /// The key to get. /// The entry if found, null otherwise. /// True if the entry exists, false otherwise. - bool TryGet(StorageKey key, out StorageItem item); + bool TryGet(StorageKey key, [NotNullWhen(true)] out StorageItem? item); + + /// + /// Seeks to the entry with the specified key. + /// + /// + /// The keyPrefix to be sought. + /// If keyPrefix is null or empty, it will seek to the first key. + /// + /// The direction of seek. + /// + /// An enumerator containing all the entries after keyOrPrefix(Forward) or before keyOrPrefix(Backward). + /// + IEnumerable<(StorageKey Key, StorageItem Value)> Seek( + byte[]? keyOrPrefix = null, SeekDirection direction = SeekDirection.Forward); } } diff --git a/src/Neo/Persistence/ReadOnlyStoreView.cs b/src/Neo/Persistence/ReadOnlyStoreView.cs index bd37d77719..fd2f966ca1 100644 --- a/src/Neo/Persistence/ReadOnlyStoreView.cs +++ b/src/Neo/Persistence/ReadOnlyStoreView.cs @@ -9,22 +9,32 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +#nullable enable + using Neo.SmartContract; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace Neo.Persistence { + /// + /// A read-only view of a store. + /// No cache and lock in this implementation, + /// so it is faster in some cases(For example, no repeated reads of the same key). + /// public class ReadOnlyStoreView : IReadOnlyStoreView { - private readonly IReadOnlyStore store; + private readonly IReadOnlyStore _store; public ReadOnlyStoreView(IReadOnlyStore store) { - this.store = store; + _store = store; } /// - public bool Contains(StorageKey key) => store.Contains(key.ToArray()); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(StorageKey key) => _store.Contains(key.ToArray()); /// public StorageItem this[StorageKey key] @@ -38,11 +48,19 @@ public StorageItem this[StorageKey key] } /// - public bool TryGet(StorageKey key, out StorageItem item) + public bool TryGet(StorageKey key, [NotNullWhen(true)] out StorageItem? item) { - var ok = store.TryGet(key.ToArray(), out byte[] value); + var ok = _store.TryGet(key.ToArray(), out var value); item = ok ? new StorageItem(value) : null; return ok; } + + /// + public IEnumerable<(StorageKey Key, StorageItem Value)> Seek( + byte[]? keyOrPrefix = null, SeekDirection direction = SeekDirection.Forward) + { + foreach (var (key, value) in _store.Seek(keyOrPrefix, direction)) + yield return (new(key), new(value)); + } } } diff --git a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs new file mode 100644 index 0000000000..fdbfa3a1bc --- /dev/null +++ b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs @@ -0,0 +1,111 @@ +// Copyright (C) 2015-2025 The Neo Project. +// +// UT_ReadOnlyViewExtension.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; +using Neo.Persistence; +using Neo.SmartContract; +using System; +using System.Linq; + +namespace Neo.UnitTests.Extensions +{ + [TestClass] + public class UT_ReadOnlyViewExtension + { + [TestMethod] + public void TestScan() + { + using var store = new MemoryStore(); + + var key1 = StorageKey.CreateSearchPrefix(1, [1]); + var key2 = StorageKey.CreateSearchPrefix(1, [2]); + var key3 = StorageKey.CreateSearchPrefix(2, [3]); + var key4 = StorageKey.CreateSearchPrefix(2, [4]); + + store.Put(key1, [1, 2, 3]); + store.Put(key2, [4, 5, 6]); + store.Put(key3, [7, 8, 9]); + store.Put(key4, [10, 11, 12]); + + var view = new ReadOnlyStoreView(store); + var items = view.ScanPrefix(StorageKey.CreateSearchPrefix(1, [])).ToArray(); + Assert.AreEqual(2, items.Length); + items[0].Key.ToArray().Should().BeEquivalentTo(key1); + items[0].Value.ToArray().Should().BeEquivalentTo([1, 2, 3]); + items[1].Key.ToArray().Should().BeEquivalentTo(key2); + items[1].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + + items = view.ScanPrefix(StorageKey.CreateSearchPrefix(1, []), SeekDirection.Backward).ToArray(); + Assert.AreEqual(2, items.Length); + items[0].Key.ToArray().Should().BeEquivalentTo(key2); + items[0].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + items[1].Key.ToArray().Should().BeEquivalentTo(key1); + items[1].Value.ToArray().Should().BeEquivalentTo([1, 2, 3]); + + items = view.ScanRange(key1, key4).ToArray(); + Assert.AreEqual(3, items.Length); + items[0].Key.ToArray().Should().BeEquivalentTo(key1); + items[0].Value.ToArray().Should().BeEquivalentTo([1, 2, 3]); + items[1].Key.ToArray().Should().BeEquivalentTo(key2); + items[1].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + items[2].Key.ToArray().Should().BeEquivalentTo(key3); + items[2].Value.ToArray().Should().BeEquivalentTo([7, 8, 9]); + + items = view.ScanRange(key4, key1, SeekDirection.Backward).ToArray(); + Assert.AreEqual(3, items.Length); + items[0].Key.ToArray().Should().BeEquivalentTo(key4); + items[0].Value.ToArray().Should().BeEquivalentTo([10, 11, 12]); + items[1].Key.ToArray().Should().BeEquivalentTo(key3); + items[1].Value.ToArray().Should().BeEquivalentTo([7, 8, 9]); + items[2].Key.ToArray().Should().BeEquivalentTo(key2); + items[2].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + } + + [TestMethod] + public void TestFindEmptyPrefix() + { + using var store = new MemoryStore(); + using var dataCache = new SnapshotCache(store); + + var k1 = StorageKey.CreateSearchPrefix(-1, []); + var k2 = StorageKey.CreateSearchPrefix(-1, [0x01]); + var k3 = StorageKey.CreateSearchPrefix(-1, [0xff, 0x02]); + + dataCache.Add(k1, new StorageItem([1, 2, 3])); + dataCache.Add(k2, new StorageItem([4, 5, 6])); + dataCache.Add(k3, new StorageItem([7, 8, 9])); + + var items = dataCache.Find().ToArray(); + items.Length.Should().Be(3); + items[0].Key.ToArray().Should().BeEquivalentTo(k1.ToArray()); + items[1].Key.ToArray().Should().BeEquivalentTo(k2.ToArray()); + items[2].Key.ToArray().Should().BeEquivalentTo(k3.ToArray()); + + items = dataCache.Find([0xff, 0xff, 0xff, 0xff, 0xff]).ToArray(); + // all 0xff prefix with backward is not supported for old code + Action action = () => dataCache.Find(null, SeekDirection.Backward); + action.Should().Throw(); + + // null and empty are not supported for backwards direction now. + action = () => dataCache.Find(null, SeekDirection.Backward); + action.Should().Throw(); + + action = () => dataCache.Find(new byte[] { }, SeekDirection.Backward); + action.Should().Throw(); + + action = () => dataCache.Find([0xff], SeekDirection.Backward).ToArray(); + action.Should().Throw(); + } + } +} + From 6ccb8ca26f40cb374a429aec05d1d6bb96809432 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 22 Jan 2025 08:30:27 +0800 Subject: [PATCH 02/14] improve: scan operations for ReadOnlyStoreView --- src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs | 5 +++++ .../Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index 02e7483b7c..d499ed22c6 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -129,9 +129,14 @@ internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, int maxSizeWhenAll0 if (seekPrefix == null) { if (maxSizeWhenAll0xff > 0) + { seekPrefix = new byte[maxSizeWhenAll0xff]; + Array.Fill(seekPrefix, (byte)0xff); + } else + { throw new ArgumentException($"{nameof(keyPrefix)} with all bytes being 0xff is not supported now"); + } } return seekPrefix; } diff --git a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs index fdbfa3a1bc..7ee3f498ef 100644 --- a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs +++ b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs @@ -69,6 +69,14 @@ public void TestScan() items[1].Value.ToArray().Should().BeEquivalentTo([7, 8, 9]); items[2].Key.ToArray().Should().BeEquivalentTo(key2); items[2].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + + // ScanPrefix with all 0xff and bacword is ok. + var key5 = StorageKey.CreateSearchPrefix(-1, [5]); + store.Put(key5, [0xf1]); + items = view.ScanPrefix([0xff], SeekDirection.Backward).ToArray(); + Assert.AreEqual(1, items.Length); + items[0].Key.ToArray().Should().BeEquivalentTo(key5); + items[0].Value.ToArray().Should().BeEquivalentTo([0xf1]); } [TestMethod] From 5628012145ded7a109cbee08b43f257d567b5559 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 22 Jan 2025 08:46:14 +0800 Subject: [PATCH 03/14] Improve: add more comments --- .../Persistence/ReadOnlyViewExtensions.cs | 74 +++++++++++++------ 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index d499ed22c6..c8053571c9 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -22,22 +22,37 @@ namespace Neo.Extensions { public static class ReadOnlyViewExtensions { + /// + /// Gets the 0xff bytes of the specified length. + /// + /// The length of the bytes. + /// The 0xff bytes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] Get0xffBytes(int length) + { + var bytes = new byte[length]; + Array.Fill(bytes, (byte)0xff); + return bytes; + } + /// /// Scans the entries starting with the specified prefix. + /// + /// If is , + /// it seeks to the first entry if is null or empty. + /// + /// + /// If is , + /// the cannot be null or empty. + /// + /// + /// If want to scan all entries with , + /// set to be N * 0xff, where N is the max length of the key. + /// See . + /// /// /// The view to scan. - /// - /// The prefix of the key. - /// - /// If is , - /// the cannot be null or empty. - /// - /// If is , - /// it seeks to the first entry if is null or empty. - /// - /// If want to scan all entries with backward, - /// set to be n * 0xff, where n is the max length of the key. - /// + /// The prefix of the key. /// The search direction. /// The entries found with the desired prefix. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -67,6 +82,19 @@ public static class ReadOnlyViewExtensions /// /// Scans the entries in the specified range. + /// + /// If is , + /// it seeks to the first entry if is null or empty. + /// + /// + /// If is , + /// the cannot be null or empty. + /// + /// + /// If want to scan all entries with , + /// set to be N * 0xff and to be empty, + /// where N is the max length of the key. + /// /// /// The view to scan. /// The inclusive start key. @@ -93,14 +121,17 @@ public static class ReadOnlyViewExtensions /// /// Gets the seek prefix for the specified key prefix. + /// + /// If the is all 0xff, and > 0, + /// the seek prefix will be set to be byte[maxSizeWhenAll0xff] and filled with 0xff. + /// + /// + /// If the is all 0xff and is less than or equal to 0, + /// an ArgumentException will be thrown. + /// /// /// The key prefix. - /// - /// The maximum size when all bytes are 0xff. - /// If the key prefix is all 0xff, and > 0, - /// the seek prefix will be set to be byte[maxSizeWhenAll0xff] and filled with 0xff. - /// If is less than or equal to 0, an ArgumentException will be thrown. - /// + /// The maximum size when all bytes are 0xff. /// The seek prefix. /// Thrown when is null. /// Thrown when is empty. @@ -129,14 +160,9 @@ internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, int maxSizeWhenAll0 if (seekPrefix == null) { if (maxSizeWhenAll0xff > 0) - { - seekPrefix = new byte[maxSizeWhenAll0xff]; - Array.Fill(seekPrefix, (byte)0xff); - } + seekPrefix = Get0xffBytes(maxSizeWhenAll0xff); else - { throw new ArgumentException($"{nameof(keyPrefix)} with all bytes being 0xff is not supported now"); - } } return seekPrefix; } From a81b8f61941fb6da6c8405c9299d6a4e4d6017a2 Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 22 Jan 2025 00:32:12 -0800 Subject: [PATCH 04/14] Update src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs --- src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index c8053571c9..c0f5071c8a 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -28,7 +28,7 @@ public static class ReadOnlyViewExtensions /// The length of the bytes. /// The 0xff bytes. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] Get0xffBytes(int length) + internal static byte[] Get0xffBytes(int length) { var bytes = new byte[length]; Array.Fill(bytes, (byte)0xff); From 86a8d750a4d3f8c165f950ea422ade7fa76dd66a Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 22 Jan 2025 22:02:49 +0800 Subject: [PATCH 05/14] Improve: use ArrayExtensions.Repeat instead --- src/Neo.Extensions/ArrayExtensions.cs | 27 +++++++++++++++++ .../Persistence/ReadOnlyViewExtensions.cs | 17 ++--------- .../Neo.Extensions.Tests/UT_ArrayExtenions.cs | 30 +++++++++++++++++++ 3 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 src/Neo.Extensions/ArrayExtensions.cs create mode 100644 tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs diff --git a/src/Neo.Extensions/ArrayExtensions.cs b/src/Neo.Extensions/ArrayExtensions.cs new file mode 100644 index 0000000000..d0dd411ab1 --- /dev/null +++ b/src/Neo.Extensions/ArrayExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2015-2025 The Neo Project. +// +// ArrayExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; + +namespace Neo.Extensions +{ + public static class ArrayExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T[] Repeat(this T value, int count) + { + T[] array = new T[count]; + Array.Fill(array, value); + return array; + } + } +} diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index c0f5071c8a..130bd28fa2 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -22,19 +22,6 @@ namespace Neo.Extensions { public static class ReadOnlyViewExtensions { - /// - /// Gets the 0xff bytes of the specified length. - /// - /// The length of the bytes. - /// The 0xff bytes. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static byte[] Get0xffBytes(int length) - { - var bytes = new byte[length]; - Array.Fill(bytes, (byte)0xff); - return bytes; - } - /// /// Scans the entries starting with the specified prefix. /// @@ -48,7 +35,7 @@ internal static byte[] Get0xffBytes(int length) /// /// If want to scan all entries with , /// set to be N * 0xff, where N is the max length of the key. - /// See . + /// See . /// /// /// The view to scan. @@ -160,7 +147,7 @@ internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, int maxSizeWhenAll0 if (seekPrefix == null) { if (maxSizeWhenAll0xff > 0) - seekPrefix = Get0xffBytes(maxSizeWhenAll0xff); + seekPrefix = ((byte)0xff).Repeat(maxSizeWhenAll0xff); else throw new ArgumentException($"{nameof(keyPrefix)} with all bytes being 0xff is not supported now"); } diff --git a/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs b/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs new file mode 100644 index 0000000000..4202858ab9 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2015-2025 The Neo Project. +// +// UT_ArrayExtenions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using FluentAssertions; +using Neo.Extensions; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_ArrayExtenions + { + [TestMethod] + public void TestRepeat() + { + var items = ((byte)0xff).Repeat(10); + items.Should().Equal([(byte)0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + + items = ((byte)0xff).Repeat(0); + items.Should().Equal([]); + } + } +} \ No newline at end of file From 77fb84370b098c7ee17b4ccf280e1ec757727153 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 22 Jan 2025 22:04:05 +0800 Subject: [PATCH 06/14] Improve: use ArrayExtensions.Repeat instead --- src/Neo.Extensions/ArrayExtensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Neo.Extensions/ArrayExtensions.cs b/src/Neo.Extensions/ArrayExtensions.cs index d0dd411ab1..21e9d80a10 100644 --- a/src/Neo.Extensions/ArrayExtensions.cs +++ b/src/Neo.Extensions/ArrayExtensions.cs @@ -16,6 +16,13 @@ namespace Neo.Extensions { public static class ArrayExtensions { + /// + /// Creates an array of the specified length, filled with the specified value. + /// + /// The type of the array elements. + /// The value to fill the array with. + /// The number of elements in the array. + /// An array of the specified length, filled with the specified value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] Repeat(this T value, int count) { From 7477f877a906048422f1b610f1dc2ebc5087a01f Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 22 Jan 2025 22:09:32 +0800 Subject: [PATCH 07/14] Fix code format and add more comments --- src/Neo/Persistence/IReadOnlyStoreView.cs | 13 +++++++------ tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Neo/Persistence/IReadOnlyStoreView.cs b/src/Neo/Persistence/IReadOnlyStoreView.cs index ac00fdc476..7a90d40526 100644 --- a/src/Neo/Persistence/IReadOnlyStoreView.cs +++ b/src/Neo/Persistence/IReadOnlyStoreView.cs @@ -48,16 +48,17 @@ public interface IReadOnlyStoreView /// /// Seeks to the entry with the specified key. + /// + /// If keyPrefix is null or empty, it will seek to the first key(even if the direction is backward). + /// So if seek to the last, keyPrefix should be N * 0xff, and N is the max length of the key. + /// /// - /// - /// The keyPrefix to be sought. - /// If keyPrefix is null or empty, it will seek to the first key. - /// + /// The keyPrefix to be sought. /// The direction of seek. /// - /// An enumerator containing all the entries after keyOrPrefix(Forward) or before keyOrPrefix(Backward). + /// An enumerator containing all the entries after keyPrefix(Forward) or before keyPrefix(Backward). /// IEnumerable<(StorageKey Key, StorageItem Value)> Seek( - byte[]? keyOrPrefix = null, SeekDirection direction = SeekDirection.Forward); + byte[]? keyPrefix = null, SeekDirection direction = SeekDirection.Forward); } } diff --git a/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs b/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs index 4202858ab9..ea723f1c24 100644 --- a/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs +++ b/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs @@ -27,4 +27,4 @@ public void TestRepeat() items.Should().Equal([]); } } -} \ No newline at end of file +} From 3dde7428c8df3bf20d647c71e28d083420ecf571 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 23 Jan 2025 19:16:18 +0800 Subject: [PATCH 08/14] Update src/Neo/Persistence/ReadOnlyStoreView.cs Co-authored-by: Christopher Schuchardt --- src/Neo/Persistence/ReadOnlyStoreView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Persistence/ReadOnlyStoreView.cs b/src/Neo/Persistence/ReadOnlyStoreView.cs index fd2f966ca1..a70d4adc7b 100644 --- a/src/Neo/Persistence/ReadOnlyStoreView.cs +++ b/src/Neo/Persistence/ReadOnlyStoreView.cs @@ -60,7 +60,7 @@ public bool TryGet(StorageKey key, [NotNullWhen(true)] out StorageItem? item) byte[]? keyOrPrefix = null, SeekDirection direction = SeekDirection.Forward) { foreach (var (key, value) in _store.Seek(keyOrPrefix, direction)) - yield return (new(key), new(value)); + yield return new(new(key), new(value)); } } } From 746b2ad48ca753121b9114fe3d805c3f2218d5cb Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 23 Jan 2025 19:16:41 +0800 Subject: [PATCH 09/14] Update src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs Co-authored-by: Christopher Schuchardt --- src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index 130bd28fa2..f246c034f1 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -61,7 +61,7 @@ public static class ReadOnlyViewExtensions foreach (var (key, value) in view.Seek(seekPrefix, direction)) { if (keyPrefix == null || key.ToArray().AsSpan().StartsWith(keyPrefix)) - yield return (key, value); + yield return new(key, value); else if (direction == SeekDirection.Forward || (seekPrefix == null || !key.ToArray().SequenceEqual(seekPrefix))) yield break; } From c06a38db2a386103fa7c55359267f38c50908cdd Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 23 Jan 2025 19:17:25 +0800 Subject: [PATCH 10/14] Update src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs Co-authored-by: Christopher Schuchardt --- src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index f246c034f1..2495109606 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -100,7 +100,7 @@ public static class ReadOnlyViewExtensions foreach (var (key, value) in view.Seek(inclusiveStartKey, direction)) { if (comparer.Compare(key.ToArray(), exclusiveEndKey) < 0) - yield return (key, value); + yield return new(key, value); else yield break; } From 1eb7e1ed933e4440f1b7d25284a65c37ca628535 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 24 Jan 2025 17:24:13 +0800 Subject: [PATCH 11/14] Update src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs Co-authored-by: Shargon --- src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index 2495109606..d3632c6c72 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -125,7 +125,7 @@ public static class ReadOnlyViewExtensions /// /// Thrown when is all 0xff and is less than or equal to 0. /// - internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, int maxSizeWhenAll0xff = 4096 /* make it long enough */) + internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, ushort maxSizeWhenAll0xff = 4096 /* make it long enough */) { if (keyPrefix == null) // Backwards seek for null prefix is not supported for now. throw new ArgumentNullException(nameof(keyPrefix)); From 5beabbc5514459bc353194e900c3523a3beb4637 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 31 Jan 2025 11:31:47 +0800 Subject: [PATCH 12/14] Update src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs Co-authored-by: Christopher Schuchardt --- src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs index d3632c6c72..2fc7c70b05 100644 --- a/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs +++ b/src/Neo/Extensions/Persistence/ReadOnlyViewExtensions.cs @@ -149,7 +149,7 @@ internal static byte[] GetSeekPrefix(this byte[]? keyPrefix, ushort maxSizeWhenA if (maxSizeWhenAll0xff > 0) seekPrefix = ((byte)0xff).Repeat(maxSizeWhenAll0xff); else - throw new ArgumentException($"{nameof(keyPrefix)} with all bytes being 0xff is not supported now"); + throw new NotSupportedException("Array filled with max value (0xFF)", new ArgumentException(nameof(keyPrefix))); } return seekPrefix; } From 4d7f58a73a125685fbb0df0b5aada4cea92f7744 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 31 Jan 2025 15:37:51 +0800 Subject: [PATCH 13/14] Fix: unit test issue --- tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs index 7ee3f498ef..4f00cb1a00 100644 --- a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs +++ b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs @@ -112,7 +112,7 @@ public void TestFindEmptyPrefix() action.Should().Throw(); action = () => dataCache.Find([0xff], SeekDirection.Backward).ToArray(); - action.Should().Throw(); + action.Should().Throw(); } } } From a417172de3b9e7a55bdd4d49ee95ffd9fdb72654 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Sat, 1 Feb 2025 14:01:56 +0800 Subject: [PATCH 14/14] fix: merge conflict --- .../Neo.Extensions.Tests/UT_ArrayExtenions.cs | 8 +-- .../Cryptography/UT_MerkleTreeNode.cs | 1 - .../Neo.UnitTests/Cryptography/UT_Murmur32.cs | 1 - .../Extensions/UT_ReadOnlyViewExtension.cs | 66 +++++++++---------- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs b/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs index ea723f1c24..4a4625020c 100644 --- a/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs +++ b/tests/Neo.Extensions.Tests/UT_ArrayExtenions.cs @@ -9,8 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using FluentAssertions; -using Neo.Extensions; +using System; +using System.Linq; namespace Neo.Extensions.Tests { @@ -21,10 +21,10 @@ public class UT_ArrayExtenions public void TestRepeat() { var items = ((byte)0xff).Repeat(10); - items.Should().Equal([(byte)0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + Assert.IsTrue(items.SequenceEqual(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })); items = ((byte)0xff).Repeat(0); - items.Should().Equal([]); + Assert.IsTrue(items.SequenceEqual(new byte[] { })); } } } diff --git a/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs b/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs index 97ebf0a35e..1d08551330 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs @@ -9,7 +9,6 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using System.Text; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs index f2104c4b6f..11797218fb 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs @@ -9,7 +9,6 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using System.Buffers.Binary; diff --git a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs index 4f00cb1a00..d4789f9c88 100644 --- a/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs +++ b/tests/Neo.UnitTests/Extensions/UT_ReadOnlyViewExtension.cs @@ -9,7 +9,6 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Extensions; using Neo.Persistence; @@ -40,43 +39,43 @@ public void TestScan() var view = new ReadOnlyStoreView(store); var items = view.ScanPrefix(StorageKey.CreateSearchPrefix(1, [])).ToArray(); Assert.AreEqual(2, items.Length); - items[0].Key.ToArray().Should().BeEquivalentTo(key1); - items[0].Value.ToArray().Should().BeEquivalentTo([1, 2, 3]); - items[1].Key.ToArray().Should().BeEquivalentTo(key2); - items[1].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + Assert.IsTrue(items[0].Key.ToArray().SequenceEqual(key1.ToArray())); + Assert.IsTrue(items[0].Value.ToArray().SequenceEqual(new byte[] { 1, 2, 3 })); + Assert.IsTrue(items[1].Key.ToArray().SequenceEqual(key2.ToArray())); + Assert.IsTrue(items[1].Value.ToArray().SequenceEqual(new byte[] { 4, 5, 6 })); items = view.ScanPrefix(StorageKey.CreateSearchPrefix(1, []), SeekDirection.Backward).ToArray(); Assert.AreEqual(2, items.Length); - items[0].Key.ToArray().Should().BeEquivalentTo(key2); - items[0].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); - items[1].Key.ToArray().Should().BeEquivalentTo(key1); - items[1].Value.ToArray().Should().BeEquivalentTo([1, 2, 3]); + Assert.IsTrue(items[0].Key.ToArray().SequenceEqual(key2.ToArray())); + Assert.IsTrue(items[0].Value.ToArray().SequenceEqual(new byte[] { 4, 5, 6 })); + Assert.IsTrue(items[1].Key.ToArray().SequenceEqual(key1.ToArray())); + Assert.IsTrue(items[1].Value.ToArray().SequenceEqual(new byte[] { 1, 2, 3 })); items = view.ScanRange(key1, key4).ToArray(); Assert.AreEqual(3, items.Length); - items[0].Key.ToArray().Should().BeEquivalentTo(key1); - items[0].Value.ToArray().Should().BeEquivalentTo([1, 2, 3]); - items[1].Key.ToArray().Should().BeEquivalentTo(key2); - items[1].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); - items[2].Key.ToArray().Should().BeEquivalentTo(key3); - items[2].Value.ToArray().Should().BeEquivalentTo([7, 8, 9]); + Assert.IsTrue(items[0].Key.ToArray().SequenceEqual(key1.ToArray())); + Assert.IsTrue(items[0].Value.ToArray().SequenceEqual(new byte[] { 1, 2, 3 })); + Assert.IsTrue(items[1].Key.ToArray().SequenceEqual(key2.ToArray())); + Assert.IsTrue(items[1].Value.ToArray().SequenceEqual(new byte[] { 4, 5, 6 })); + Assert.IsTrue(items[2].Key.ToArray().SequenceEqual(key3.ToArray())); + Assert.IsTrue(items[2].Value.ToArray().SequenceEqual(new byte[] { 7, 8, 9 })); items = view.ScanRange(key4, key1, SeekDirection.Backward).ToArray(); Assert.AreEqual(3, items.Length); - items[0].Key.ToArray().Should().BeEquivalentTo(key4); - items[0].Value.ToArray().Should().BeEquivalentTo([10, 11, 12]); - items[1].Key.ToArray().Should().BeEquivalentTo(key3); - items[1].Value.ToArray().Should().BeEquivalentTo([7, 8, 9]); - items[2].Key.ToArray().Should().BeEquivalentTo(key2); - items[2].Value.ToArray().Should().BeEquivalentTo([4, 5, 6]); + Assert.IsTrue(items[0].Key.ToArray().SequenceEqual(key4.ToArray())); + Assert.IsTrue(items[0].Value.ToArray().SequenceEqual(new byte[] { 10, 11, 12 })); + Assert.IsTrue(items[1].Key.ToArray().SequenceEqual(key3.ToArray())); + Assert.IsTrue(items[1].Value.ToArray().SequenceEqual(new byte[] { 7, 8, 9 })); + Assert.IsTrue(items[2].Key.ToArray().SequenceEqual(key2.ToArray())); + Assert.IsTrue(items[2].Value.ToArray().SequenceEqual(new byte[] { 4, 5, 6 })); // ScanPrefix with all 0xff and bacword is ok. var key5 = StorageKey.CreateSearchPrefix(-1, [5]); store.Put(key5, [0xf1]); items = view.ScanPrefix([0xff], SeekDirection.Backward).ToArray(); Assert.AreEqual(1, items.Length); - items[0].Key.ToArray().Should().BeEquivalentTo(key5); - items[0].Value.ToArray().Should().BeEquivalentTo([0xf1]); + Assert.IsTrue(items[0].Key.ToArray().SequenceEqual(key5.ToArray())); + Assert.IsTrue(items[0].Value.ToArray().SequenceEqual(new byte[] { 0xf1 })); } [TestMethod] @@ -94,25 +93,20 @@ public void TestFindEmptyPrefix() dataCache.Add(k3, new StorageItem([7, 8, 9])); var items = dataCache.Find().ToArray(); - items.Length.Should().Be(3); - items[0].Key.ToArray().Should().BeEquivalentTo(k1.ToArray()); - items[1].Key.ToArray().Should().BeEquivalentTo(k2.ToArray()); - items[2].Key.ToArray().Should().BeEquivalentTo(k3.ToArray()); - - items = dataCache.Find([0xff, 0xff, 0xff, 0xff, 0xff]).ToArray(); - // all 0xff prefix with backward is not supported for old code - Action action = () => dataCache.Find(null, SeekDirection.Backward); - action.Should().Throw(); + Assert.AreEqual(3, items.Length); + Assert.IsTrue(items[0].Key.ToArray().SequenceEqual(k1.ToArray())); + Assert.IsTrue(items[1].Key.ToArray().SequenceEqual(k2.ToArray())); + Assert.IsTrue(items[2].Key.ToArray().SequenceEqual(k3.ToArray())); // null and empty are not supported for backwards direction now. - action = () => dataCache.Find(null, SeekDirection.Backward); - action.Should().Throw(); + Action action = () => dataCache.Find(null, SeekDirection.Backward); + Assert.ThrowsException(action); action = () => dataCache.Find(new byte[] { }, SeekDirection.Backward); - action.Should().Throw(); + Assert.ThrowsException(action); action = () => dataCache.Find([0xff], SeekDirection.Backward).ToArray(); - action.Should().Throw(); + Assert.ThrowsException(action); } } }