using System; using System.Collections.Generic; using System.Linq; using System.Threading; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace NitroxModel.DataStructures { [TestClass] public class ThreadSafeSetTest { private ThreadSafeSet set; [TestInitialize] public void Setup() { set = new ThreadSafeSet(); for (int i = 0; i < 10; i++) { set.Add($"test {i}"); } } [TestMethod] public void Remove() { set.Should().Contain("test 0"); set.Remove("test 0"); set.Should().NotContain("test 0"); } [TestMethod] public void Contains() { set.Contains("test 1").Should().BeTrue(); } [TestMethod] public void Except() { string[] exclude = { "test 0", "test 5", "test 9" }; set.Should().Contain(exclude); set.ExceptWith(exclude); set.Should().NotContain(exclude); } [TestMethod] public void ReadAndWriteSimultaneous() { int iterations = 500000; ThreadSafeSet comeGetMe = new(); List countsRead = new(); long addCount = 0; Random r = new Random(); DoReaderWriter(() => { countsRead.Add(Interlocked.Read(ref addCount)); }, i => { comeGetMe.Add(new string(Enumerable.Repeat(' ', 10).Select(c => (char)r.Next('A', 'Z')).ToArray())); Interlocked.Increment(ref addCount); }, iterations); addCount.Should().Be(iterations); countsRead.Count.Should().BeGreaterThan(0); countsRead.Last().Should().Be(iterations); comeGetMe.Count.Should().Be(iterations); } [TestMethod] public void IterateAndAddSimultaneous() { int iterations = 500000; ThreadSafeSet comeGetMe = new(); long addCount = 0; long iterationsReadMany = 0; Random r = new(); DoReaderWriter(() => { foreach (int item in comeGetMe) { Interlocked.Increment(ref iterationsReadMany); } }, i => { comeGetMe.Add(r.Next()); Interlocked.Increment(ref addCount); }, iterations); addCount.Should().Be(iterations); iterationsReadMany.Should().BePositive(); } [TestMethod] public void IterateAndAdd() { ThreadSafeSet nums = new() { 1,2,3,4,5 }; foreach (int num in nums) { if (num == 3) { nums.Add(10); } } nums.Count.Should().Be(6); nums.Last().Should().Be(10); } private void DoReaderWriter(Action reader, Action writer, int iterators) { ManualResetEventSlim barrier = new(false); Thread readerThread = new(() => { while (!barrier.IsSet) { reader(); Thread.Yield(); } // Read one last time after writer finishes reader(); }); Thread writerThread = new(() => { for (int i = 0; i < iterators; i++) { writer(i); } barrier.Set(); }); readerThread.Start(); writerThread.Start(); barrier.Wait(); } } }