Try your search with a different keyword or use * as a wildcard.
using System.Collections.Concurrent;
using System.Diagnostics;
using FluentAssertions;
using Nop.Core.Infrastructure;
using NUnit.Framework;
namespace Nop.Tests.Nop.Core.Tests.Infrastructure;
[TestFixture]
public class ConcurrentTrieTests
{
private IConcurrentCollection _sut;
private static void Profile(Action action)
{
var sw = new Stopwatch();
var memory = GC.GetTotalMemory(true) / 1024.0 / 1024.0;
sw.Start();
action.Invoke();
sw.Stop();
var delta = GC.GetTotalMemory(true) / 1024.0 / 1024.0 - memory;
Console.WriteLine("Elapsed time: {0:F} s", sw.ElapsedMilliseconds / 1000.0);
Console.WriteLine("Memory usage: {0:F} MB", delta);
}
[SetUp]
public void SetUp()
{
_sut = new ConcurrentTrie();
}
[Test]
public void CanAddAndGetValue()
{
_sut.TryGetValue("a", out _).Should().BeFalse();
_sut.Add("a", 1);
_sut.TryGetValue("a", out var value).Should().BeTrue();
value.Should().Be(1);
_sut.Add("a", 2);
_sut.TryGetValue("a", out value).Should().BeTrue();
value.Should().Be(2);
}
[Test]
public void CanAddAndGetValues()
{
_sut.Add("a", 1);
_sut.TryGetValue("ab", out _).Should().BeFalse();
_sut.Add("abc", 3);
_sut.TryGetValue("ab", out _).Should().BeFalse();
_sut.TryGetValue("a", out var value).Should().BeTrue();
value.Should().Be(1);
_sut.TryGetValue("abc", out value).Should().BeTrue();
value.Should().Be(3);
_sut.Add("ab", 2);
_sut.TryGetValue("ab", out value).Should().BeTrue();
value.Should().Be(2);
}
[Test]
public void DoesNotBlockWhileEnumerating()
{
_sut.Add("a", 0);
_sut.Add("ab", 0);
foreach (var item in _sut.Keys)
_sut.Remove(item);
}
[Test]
public void CanRemoveValue()
{
_sut.Add("a", 1);
_sut.Add("b", 1);
_sut.Add("bbb", 1);
_sut.Add("ab", 1);
_sut.Add("aa", 1);
_sut.Add("abc", 1);
_sut.Add("abb", 1);
_sut.Remove("ab");
_sut.TryGetValue("ab", out _).Should().BeFalse();
_sut.Keys.Should().BeEquivalentTo(["abc", "a", "b", "aa", "abb", "bbb"]);
Assert.DoesNotThrow(() => _sut.Remove("ab"));
_sut.Remove("bb");
_sut.TryGetValue("b", out _).Should().BeTrue();
_sut.TryGetValue("bbb", out _).Should().BeTrue();
_sut.Prune("b", out _sut);
_sut.Keys.Should().BeEquivalentTo(["b", "bbb"]);
_sut.Remove("b");
_sut.Keys.Should().BeEquivalentTo(["bbb"]);
}
[Test]
public void CanGetKeys()
{
var keys = new[] { "a", "b", "abc" };
foreach (var key in keys)
_sut.Add(key, 1);
_sut.Keys.Should().BeEquivalentTo(keys);
}
[Test]
public void CanPrune()
{
_sut.Add("a", 1);
_sut.Add("b", 1);
_sut.Add("bba", 1);
_sut.Add("bbb", 1);
_sut.Add("ab", 1);
_sut.Add("abc", 1);
_sut.Add("abd", 1);
_sut.Prune("bc", out _).Should().BeFalse();
_sut.Prune("ab", out var subtree).Should().BeTrue();
subtree.Keys.Should().BeEquivalentTo(["ab", "abc", "abd"]);
_sut.Keys.Should().BeEquivalentTo(["a", "b", "bba", "bbb"]);
_sut.Prune("b", out subtree).Should().BeTrue();
subtree.Keys.Should().BeEquivalentTo(["b", "bba", "bbb"]);
_sut.Keys.Should().BeEquivalentTo(["a"]);
_sut = subtree;
_sut.Prune("bb", out subtree).Should().BeTrue();
subtree.Keys.Should().BeEquivalentTo(["bba", "bbb"]);
_sut.Keys.Should().BeEquivalentTo(["b"]);
_sut = subtree;
_sut.Prune("bba", out subtree);
subtree.Keys.Should().BeEquivalentTo(["bba"]);
_sut.Keys.Should().BeEquivalentTo(["bbb"]);
_sut = new ConcurrentTrie();
_sut.Add("aaa", 1);
_sut.Prune("a", out subtree).Should().BeTrue();
subtree.Keys.Should().BeEquivalentTo(["aaa"]);
_sut.Keys.Should().BeEmpty();
_sut = subtree;
_sut.Prune("aa", out subtree).Should().BeTrue();
_sut.Keys.Should().BeEmpty();
subtree.Keys.Should().BeEquivalentTo(["aaa"]);
}
[Test]
public void CanSearch()
{
_sut.Add("a", 1);
_sut.Add("b", 1);
_sut.Add("abc", 1);
_sut.Add("abcd", 2);
var keys = _sut.Keys.ToList();
_sut.Search("ab").Should().BeEquivalentTo(new KeyValuePair[]
{
new("abc", 1),
new("abcd", 2)
});
_sut.Keys.Should().BeEquivalentTo(keys);
}
[Test]
public void CanClear()
{
_sut.Add("a", 1);
_sut.Add("ab", 1);
_sut.Add("abc", 1);
_sut.Clear();
_sut.Keys.Should().BeEmpty();
}
[Test]
[TestCase(typeof(NopConcurrentCollection))]
[TestCase(typeof(ConcurrentTrie))]
[Ignore("Not a test, used for profiling.")]
public void Profile(Type oType)
{
var sut = Activator.CreateInstance(oType) as IConcurrentCollection;
sut.Should().NotBeNull();
Profile(() =>
{
for (var i = 0; i < 1000000; i++)
sut.Add(Guid.NewGuid().ToString(), 0);
});
}
[Test]
[TestCase(typeof(NopConcurrentCollection))]
[TestCase(typeof(ConcurrentTrie))]
[Ignore("Expensive concurrency test, enable manually.")]
public void DoesNotBreakDuringParallelAddRemove(Type oType)
{
var sut = Activator.CreateInstance(oType) as IConcurrentCollection;
sut.Should().NotBeNull();
Profile(() =>
{
Parallel.For(0, 1000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, j =>
{
for (var i = 0; i < 1000; i++)
{
var s = $"{i}-{j}";
sut.Add(s, i);
sut.TryGetValue(s, out var value).Should().BeTrue();
value.Should().Be(i);
sut.Remove(s);
sut.TryGetValue(s, out _).Should().BeFalse();
}
});
});
sut.Keys.Count().Should().Be(0);
}
[Test]
[TestCase(typeof(NopConcurrentCollection))]
[TestCase(typeof(ConcurrentTrie))]
[Ignore("Expensive concurrency test, enable manually.")]
public void DoesNotBreakDuringParallelAddPrune(Type oType)
{
var sut = Activator.CreateInstance(oType) as IConcurrentCollection;
sut.Should().NotBeNull();
Profile(() =>
{
Parallel.For(0, 1000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, j =>
{
for (var i = 0; i < 1000; i++)
{
var s = $"{j}-{i}";
sut.Add(s, i);
}
sut.Prune($"{j}-", out var st);
st.Keys.Count().Should().Be(1000);
});
});
sut.Keys.Count().Should().Be(0);
}
[Test]
[TestCase(typeof(NopConcurrentCollection))]
[TestCase(typeof(ConcurrentTrie))]
[Ignore("Not a test, used for profiling.")]
public void ProfilePrune(Type oType)
{
var sut = Activator.CreateInstance(oType) as IConcurrentCollection;
sut.Should().NotBeNull();
// insert
for (var i = 0; i < 10000; i++)
sut.Add(Guid.NewGuid().ToString(), 0);
Profile(() =>
{
Parallel.For(0, 1000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, j =>
{
for (var i = 0; i < 20; i++)
{
// insert
sut.Add(Guid.NewGuid().ToString(), 0);
// remove by prefix
sut.Prune(Guid.NewGuid().ToString()[..5], out _);
}
});
});
}
#region Nested class
///
/// A thread-safe collection based on
///
public partial class NopConcurrentCollection : IConcurrentCollection
{
#region Fields
protected readonly ConcurrentDictionary _dictionary;
#endregion
#region Ctor
///
/// Initializes a new instance of
///
protected NopConcurrentCollection(IEnumerable> subCollection)
{
_dictionary = new ConcurrentDictionary(subCollection);
}
///
/// Initializes a new empty instance of
///
public NopConcurrentCollection()
{
_dictionary = new ConcurrentDictionary();
}
#endregion
#region Methods
///
/// Attempts to get the value associated with the specified key
///
/// The key of the item to get (case-sensitive)
/// The value associated with , if found
///
/// True if the key was found, otherwise false
///
public bool TryGetValue(string key, out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
///
/// Adds a key-value pair to the collection
///
/// The key of the new item (case-sensitive)
/// The value to be associated with
public void Add(string key, TValue value)
{
_dictionary[key] = value;
}
///
/// Clears the collection
///
public void Clear()
{
_dictionary.Clear();
}
///
/// Gets all key-value pairs for keys starting with the given prefix
///
/// The prefix (case-sensitive) to search for
///
/// All key-value pairs for keys starting with
///
public IEnumerable> Search(string prefix)
{
return _dictionary.Keys.Where(k => k.StartsWith(prefix))
.Select(key => new KeyValuePair(key, _dictionary[key]));
}
///
/// Removes the item with the given key, if present
///
/// The key (case-sensitive) of the item to be removed
public void Remove(string key)
{
_dictionary.Remove(key, out _);
}
///
/// Attempts to remove all items with keys starting with the specified prefix
///
/// The prefix (case-sensitive) of the items to be deleted
/// The sub-collection containing all deleted items, if found
///
/// True if the prefix was successfully removed from the collection, otherwise false
///
public bool Prune(string prefix, out IConcurrentCollection subCollection)
{
subCollection = new NopConcurrentCollection(Search(prefix));
try
{
foreach (var key in subCollection.Keys)
Remove(key);
}
catch
{
return false;
}
return subCollection.Keys.Any();
}
#endregion
#region Properties
///
/// Gets a collection that contains the keys in the
///
public IEnumerable Keys => _dictionary.Keys;
#endregion
}
#endregion
}