This is an automated email from the ASF dual-hosted git repository.

paulirwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


The following commit(s) were added to refs/heads/master by this push:
     new 6b161d961 Add support unit tests for ConcurrentHashSet, #1117 (#1128)
6b161d961 is described below

commit 6b161d961a7764f2d2dbe90ee2ae03f73ccce019
Author: Paul Irwin <[email protected]>
AuthorDate: Sat Feb 15 08:29:04 2025 -0700

    Add support unit tests for ConcurrentHashSet, #1117 (#1128)
    
    * Add support unit tests for ConcurrentHashSet, #1117
    
    * Add Harmony Support_ unit tests, #1117
    
    * Add IReadOnlySet for .NET 5+
---
 .../Support/Support_CollectionTest.cs              | 115 ++++++
 src/Lucene.Net.Tests/Support/Support_SetTest.cs    |  48 +++
 .../Support/Support_UnmodifiableCollectionTest.cs  | 112 ++++++
 .../Support/TestConcurrentHashSet.cs               | 421 ++++++++++++++++++++-
 .../Support/Threading/JSR166TestCase.cs            |  18 +
 src/Lucene.Net/Support/ConcurrentHashSet.cs        | 104 ++++-
 6 files changed, 795 insertions(+), 23 deletions(-)

diff --git a/src/Lucene.Net.Tests/Support/Support_CollectionTest.cs 
b/src/Lucene.Net.Tests/Support/Support_CollectionTest.cs
new file mode 100644
index 000000000..4430adff3
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Support_CollectionTest.cs
@@ -0,0 +1,115 @@
+// Adapted from Apache Harmony tests via J2N: 
https://github.com/NightOwl888/J2N/blob/main/tests/NUnit/J2N.Tests/Collections/Support_CollectionTest.cs
+using Lucene.Net.Util;
+using System.Collections.Generic;
+using JCG = J2N.Collections.Generic;
+
+namespace Lucene.Net
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    public class Support_CollectionTest : LuceneTestCase
+    {
+        readonly ICollection<int> col; // must contain the Integers 0 to 99
+
+        // LUCENENET: removed unused string argument and overload
+        public Support_CollectionTest(/*String p1,*/ ICollection<int> c)
+            //: base(p1)
+        {
+            col = c;
+        }
+
+        public void RunTest()
+        {
+            new Support_UnmodifiableCollectionTest(col).RunTest();
+
+            // setup
+            ICollection<int> myCollection = new JCG.SortedSet<int>();
+            myCollection.Add(101);
+            myCollection.Add(102);
+            myCollection.Add(103);
+
+            // add
+            //assertTrue("CollectionTest - a) add did not work", col.Add(new 
Integer(
+            //        101)));
+            col.Add(101); // Does not return in .NET
+            assertTrue("CollectionTest - b) add did not work", col
+                    .Contains(101));
+
+            // remove
+            assertTrue("CollectionTest - a) remove did not work", col
+                    .Remove(101));
+            assertTrue("CollectionTest - b) remove did not work", !col
+                    .Contains(101));
+
+            if (col is ISet<int> set)
+            {
+                // addAll
+                //assertTrue("CollectionTest - a) addAll failed", set
+                //        .UnionWith(myCollection));
+                set.UnionWith(myCollection); // Does not return in .NET
+                assertTrue("CollectionTest - b) addAll failed", set
+                        .IsSupersetOf(myCollection));
+
+                // containsAll
+                assertTrue("CollectionTest - a) containsAll failed", set
+                        .IsSupersetOf(myCollection));
+                col.Remove(101);
+                assertTrue("CollectionTest - b) containsAll failed", !set
+                        .IsSupersetOf(myCollection));
+
+                // removeAll
+                //assertTrue("CollectionTest - a) removeAll failed", set
+                //        .ExceptWith(myCollection));
+                //assertTrue("CollectionTest - b) removeAll failed", !set
+                //        .ExceptWith(myCollection)); // should not change the 
colletion
+                //                                   // the 2nd time around
+
+                set.ExceptWith(myCollection); // Does not return in .NET
+                assertTrue("CollectionTest - c) removeAll failed", !set
+                        .Contains(102));
+                assertTrue("CollectionTest - d) removeAll failed", !set
+                        .Contains(103));
+
+                // retianAll
+                set.UnionWith(myCollection);
+                //assertTrue("CollectionTest - a) retainAll failed", set
+                //        .IntersectWith(myCollection));
+                //assertTrue("CollectionTest - b) retainAll failed", !set
+                //        .IntersectWith(myCollection)); // should not change 
the colletion
+                //                                   // the 2nd time around
+
+                set.IntersectWith(myCollection); // Does not return in .NET
+                assertTrue("CollectionTest - c) retainAll failed", set
+                        .IsSupersetOf(myCollection));
+                assertTrue("CollectionTest - d) retainAll failed", !set
+                        .Contains(0));
+                assertTrue("CollectionTest - e) retainAll failed", !set
+                        .Contains(50));
+
+            }
+
+            // clear
+            col.Clear();
+            assertTrue("CollectionTest - a) clear failed", col.Count == 0);
+            assertTrue("CollectionTest - b) clear failed", !col
+                    .Contains(101));
+
+        }
+
+    }
+}
diff --git a/src/Lucene.Net.Tests/Support/Support_SetTest.cs 
b/src/Lucene.Net.Tests/Support/Support_SetTest.cs
new file mode 100644
index 000000000..b07b25dab
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Support_SetTest.cs
@@ -0,0 +1,48 @@
+// Adapted from Apache Harmony tests: 
https://github.com/apache/harmony/blob/trunk/classlib/support/src/test/java/tests/support/Support_SetTest.java
+using Lucene.Net.Util;
+using System.Collections.Generic;
+
+namespace Lucene.Net
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    public class Support_SetTest : LuceneTestCase
+    {
+        ISet<int> set; // must contain the Integers 0 to 99
+
+        // LUCENENET: removed unused string argument and overload
+        public Support_SetTest(/*String p1,*/ ISet<int> s)
+            //: base(p1)
+        {
+            set = s;
+        }
+
+        public void RunTest() {
+            // add
+            assertTrue("Set Test - Adding a duplicate element changed the set",
+                !set.Add(50));
+            assertTrue("Set Test - Removing an element did not change the 
set", set
+                .Remove(50));
+            assertTrue(
+                "Set Test - Adding and removing a duplicate element failed to 
remove it",
+                !set.Contains(50));
+            set.Add(50);
+            new Support_CollectionTest(set).RunTest();
+        }
+    }
+}
diff --git a/src/Lucene.Net.Tests/Support/Support_UnmodifiableCollectionTest.cs 
b/src/Lucene.Net.Tests/Support/Support_UnmodifiableCollectionTest.cs
new file mode 100644
index 000000000..d729e25ab
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Support_UnmodifiableCollectionTest.cs
@@ -0,0 +1,112 @@
+// Adapted from Apache Harmony tests via J2N: 
https://github.com/NightOwl888/J2N/blob/main/tests/NUnit/J2N.Tests/Collections/Support_UnmodifiableCollectionTest.cs
+
+using Lucene.Net.Util;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    public class Support_UnmodifiableCollectionTest : LuceneTestCase
+    {
+        readonly ICollection<int> col;
+
+        // must be a collection containing the Integers 0 to 99 (which will 
iterate
+        // in order)
+
+        // LUCENENET: removed unused string argument and overload
+        public Support_UnmodifiableCollectionTest(/*String p1,*/ 
ICollection<int> c)
+        //: base(p1)
+        {
+            col = c;
+        }
+
+        public void RunTest()
+        {
+
+            // contains
+            assertTrue("UnmodifiableCollectionTest - should contain 0", col
+                    .Contains(0));
+            assertTrue("UnmodifiableCollectionTest - should contain 50", col
+                    .Contains(50));
+            assertTrue("UnmodifiableCollectionTest - should not contain 100", 
!col
+                    .Contains(100));
+
+            // containsAll
+            HashSet<int> hs = new HashSet<int>();
+            hs.Add(0);
+            hs.Add(25);
+            hs.Add(99);
+            assertTrue(
+                    "UnmodifiableCollectionTest - should contain set of 0, 25, 
and 99",
+                    col.Intersect(hs).Count() == hs.Count); // Contains all
+            hs.Add(100);
+            assertTrue(
+                    "UnmodifiableCollectionTest - should not contain set of 0, 
25, 99 and 100",
+                    col.Intersect(hs).Count() != hs.Count); // Doesn't contain 
all
+
+            // isEmpty
+            assertTrue("UnmodifiableCollectionTest - should not be empty", 
col.Count > 0);
+
+            // iterator
+            IEnumerator<int> it = col.GetEnumerator();
+            SortedSet<int> ss = new SortedSet<int>();
+            while (it.MoveNext())
+            {
+                ss.Add(it.Current);
+            }
+            it = ss.GetEnumerator();
+            for (int counter = 0; it.MoveNext(); counter++)
+            {
+                int nextValue = it.Current;
+                assertTrue(
+                        "UnmodifiableCollectionTest - Iterator returned wrong 
value.  Wanted: "
+                                + counter + " got: " + nextValue,
+                        nextValue == counter);
+            }
+
+            // size
+            assertTrue(
+                    "UnmodifiableCollectionTest - returned wrong size.  Wanted 
100, got: "
+                            + col.Count, col.Count == 100);
+
+            // toArray
+            object[] objArray;
+            objArray = col.Cast<object>().ToArray();
+            it = ss.GetEnumerator(); // J2N: Bug in Harmony, this needs to be 
reset to run
+            for (int counter = 0; it.MoveNext(); counter++)
+            {
+                assertTrue(
+                        "UnmodifiableCollectionTest - toArray returned 
incorrect array",
+                        (int)objArray[counter] == it.Current);
+            }
+
+            // toArray (Object[])
+            var intArray = new int[100];
+            col.CopyTo(intArray, 0);
+            it = ss.GetEnumerator(); // J2N: Bug in Harmony, this needs to be 
reset to run
+            for (int counter = 0; it.MoveNext(); counter++)
+            {
+                assertTrue(
+                        "UnmodifiableCollectionTest - CopyTo(object[], int) 
filled array incorrectly",
+                        intArray[counter] == it.Current);
+            }
+        }
+    }
+}
diff --git a/src/Lucene.Net.Tests/Support/TestConcurrentHashSet.cs 
b/src/Lucene.Net.Tests/Support/TestConcurrentHashSet.cs
index 39728c342..2caf26f86 100644
--- a/src/Lucene.Net.Tests/Support/TestConcurrentHashSet.cs
+++ b/src/Lucene.Net.Tests/Support/TestConcurrentHashSet.cs
@@ -1,8 +1,18 @@
-using Lucene.Net.Attributes;
+// Some tests adapted from Apache Harmony:
+// 
https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/concurrent/src/test/java/ConcurrentHashMapTest.java
+// 
https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/CollectionsTest.java
+
+using J2N.Threading;
+using Lucene.Net.Attributes;
 using Lucene.Net.Support;
+using Lucene.Net.Support.Threading;
 using NUnit.Framework;
+using System;
+using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using System.Threading.Tasks;
+#nullable enable
 
 namespace Lucene.Net
 {
@@ -23,8 +33,122 @@ namespace Lucene.Net
      * limitations under the License.
      */
 
-    public class TestConcurrentHashSet
+    /// <summary>
+    /// Tests for <see cref="ConcurrentHashSet{T}"/>.
+    /// </summary>
+    /// <remarks>
+    /// Some tests (those not marked with <see 
cref="LuceneNetSpecificAttribute"/>)
+    /// are adapted from Apache Harmony's ConcurrentHashMapTest class. This 
class
+    /// tests ConcurrentHashMap, which is a dictionary, but the key behavior is
+    /// similar to <see cref="ConcurrentHashSet{T}"/>.
+    /// </remarks>
+    public class TestConcurrentHashSet : JSR166TestCase
     {
+        /// <summary>
+        /// Used by <see cref="TestSynchronizedSet"/>
+        /// </summary>
+        // Ported from 
https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/CollectionsTest.java#L66-L76
+        private static readonly object[] objArray = LoadObjArray();
+
+        private static object[] LoadObjArray() // LUCENENET: avoid static 
constructors
+        {
+            object[] objArray = new object[1000];
+            for (int i = 0; i < objArray.Length; i++)
+            {
+                objArray[i] = i;
+            }
+            return objArray;
+        }
+
+        /// <summary>
+        /// Used by <see cref="TestSynchronizedSet"/>
+        /// </summary>
+        /// <remarks>
+        /// Implements ThreadJob instead of Runnable, as that's what we have 
access to here.
+        /// </remarks>
+        // Ported from 
https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/CollectionsTest.java#L88-L159
+        public class SynchCollectionChecker : ThreadJob
+        {
+            private ISet<object> col; // LUCENENET: was Collection, but we 
need to use ISet to access the IsSupersetOf method
+
+            // private int colSize; // LUCENENET: converted to local variable
+
+            private readonly int totalToRun;
+
+            private readonly bool offset;
+
+            private volatile int numberOfChecks /* = 0 */;
+
+            private bool result = true;
+
+            private readonly List<object> normalCountingList;
+
+            private readonly List<object> offsetCountingList;
+
+            public override void Run()
+            {
+                // ensure the list either contains the numbers from 0 to 
size-1 or
+                // the numbers from size to 2*size -1
+                while (numberOfChecks < totalToRun)
+                {
+                    UninterruptableMonitor.Enter(col);
+                    try
+                    {
+                        if (!(col.Count == 0
+                              || col.IsSupersetOf(normalCountingList)
+                              || col.IsSupersetOf(offsetCountingList)))
+                            result = false;
+                        col.clear();
+                    }
+                    finally
+                    {
+                        UninterruptableMonitor.Exit(col);
+                    }
+                    if (offset)
+                        col.UnionWith(offsetCountingList);
+                    else
+                        col.UnionWith(normalCountingList);
+                    Interlocked.Increment(ref numberOfChecks); // was: 
numberOfChecks++;
+                }
+            }
+
+            public SynchCollectionChecker(ISet<object> c, bool offset,
+                int totalChecks)
+            {
+                // The collection to test, whether to offset the filler values 
by
+                // size or not, and the min number of iterations to run
+                totalToRun = totalChecks;
+                col = c;
+                int colSize = c.size();
+                normalCountingList = new List<object>(colSize);
+                offsetCountingList = new List<object>(colSize);
+                for (int counter = 0; counter < colSize; counter++)
+                    normalCountingList.Add(counter);
+                for (int counter = 0; counter < colSize; counter++)
+                    offsetCountingList.Add(counter + colSize);
+                col.clear();
+                if (offset)
+                    col.UnionWith(offsetCountingList);
+                else
+                    col.UnionWith(normalCountingList);
+                this.offset = offset; // LUCENENET - this line was missing 
from the original code
+            }
+
+            public bool Offset
+                // answer true iff the list is filled with a counting sequence
+                // starting at the value size to 2*size - 1
+                // else the list with be filled starting at 0 to size - 1
+                => offset;
+
+            public bool Result
+                // answer true iff no corruption has been found in the 
collection
+                => result;
+
+            public int NumberOfChecks
+                // answer the number of checks that have been performed on the 
list
+                => numberOfChecks;
+        }
+
         [Test, LuceneNetSpecific]
         public void TestExceptWith()
         {
@@ -47,5 +171,298 @@ public void TestExceptWith()
             Assert.IsTrue(hashSet.Contains(0));
             Assert.IsTrue(hashSet.Contains(99));
         }
+
+        /// <summary>
+        /// Create a set from Integers 1-5.
+        /// </summary>
+        /// <remarks>
+        /// In the Harmony tests, this returns a ConcurrentHashMap,
+        /// hence the name. Retaining the name, even though this is not a map,
+        /// for consistency with the original tests.
+        /// </remarks>
+        private static ConcurrentHashSet<object> Map5()
+        {
+            ConcurrentHashSet<object> map = new ConcurrentHashSet<object>();
+            assertTrue(map.IsEmpty);
+            map.Add(one);
+            map.Add(two);
+            map.Add(three);
+            map.Add(four);
+            map.Add(five);
+            assertFalse(map.IsEmpty);
+            assertEquals(5, map.Count);
+            return map;
+        }
+
+        /// <summary>
+        /// clear removes all items
+        /// </summary>
+        [Test]
+        public void TestClear()
+        {
+            ConcurrentHashSet<object> map = Map5();
+            map.Clear();
+            assertEquals(map.size(), 0);
+        }
+
+        /// <summary>
+        /// Sets with same contents are equal
+        /// </summary>
+        [Test]
+        [Ignore("ConcurrentHashSet does not currently implement structural 
Equals")]
+        public void TestEquals()
+        {
+            ConcurrentHashSet<object> map1 = Map5();
+            ConcurrentHashSet<object> map2 = Map5();
+            assertEquals(map1, map2);
+            assertEquals(map2, map1);
+            map1.Clear();
+            assertFalse(map1.Equals(map2));
+            assertFalse(map2.Equals(map1));
+        }
+
+        /// <summary>
+        /// contains returns true for contained value
+        /// </summary>
+        /// <remarks>
+        /// This was <c>testContainsKey</c> in the Harmony tests,
+        /// but we're using keys as values here.
+        /// </remarks>
+        [Test]
+        public void TestContains()
+        {
+            ConcurrentHashSet<object> map = Map5();
+            assertTrue(map.Contains(one));
+            assertFalse(map.Contains(zero));
+        }
+
+        /// <summary>
+        /// enumeration returns an enumeration containing the correct
+        /// elements
+        /// </summary>
+        [Test]
+        public void TestEnumeration()
+        {
+            ConcurrentHashSet<object> map = Map5();
+            using IEnumerator<object> e = map.GetEnumerator();
+            int count = 0;
+            while (e.MoveNext())
+            {
+                count++;
+                Assert.IsNotNull(e.Current); // LUCENENET specific - original 
test did not have an assert here
+            }
+
+            assertEquals(5, count);
+        }
+
+        // LUCENENET - omitted testGet because it is not applicable to a set
+
+        /// <summary>
+        /// IsEmpty is true of empty map and false for non-empty
+        /// </summary>
+        [Test]
+        public void TestIsEmpty()
+        {
+            ConcurrentHashSet<object> empty = new ConcurrentHashSet<object>();
+            ConcurrentHashSet<object> map = Map5();
+            assertTrue(empty.IsEmpty);
+            assertFalse(map.IsEmpty);
+        }
+
+        // LUCENENET - omitted testKeys, testKeySet, testKeySetToArray, 
testValuesToArray, testEntrySetToArray,
+        // testValues, testEntrySet
+
+        /// <summary>
+        /// UnionAll adds all elements from the given set
+        /// </summary>
+        /// <remarks>
+        /// This was adapted from testPutAll in the Harmony tests.
+        /// </remarks>
+        [Test]
+        public void TestUnionWith()
+        {
+            ConcurrentHashSet<object> empty = new ConcurrentHashSet<object>();
+            ConcurrentHashSet<object> map = Map5();
+            empty.UnionWith(map);
+            assertEquals(5, empty.size());
+            assertTrue(empty.Contains(one));
+            assertTrue(empty.Contains(two));
+            assertTrue(empty.Contains(three));
+            assertTrue(empty.Contains(four));
+            assertTrue(empty.Contains(five));
+        }
+
+        // LUCENENET - omitted testPutIfAbsent, testPutIfAbsent2, testReplace, 
testReplace2, testReplaceValue, testReplaceValue2
+
+        /// <summary>
+        /// remove removes the correct value from the set
+        /// </summary>
+        [Test]
+        public void TestRemove()
+        {
+            ConcurrentHashSet<object> map = Map5();
+            map.remove(five);
+            assertEquals(4, map.size());
+            assertFalse(map.Contains(five));
+        }
+
+        /// <summary>
+        /// remove(value) removes only if value present
+        /// </summary>
+        [Test]
+        public void TestRemove2()
+        {
+            ConcurrentHashSet<object> map = Map5();
+            map.remove(five);
+            assertEquals(4, map.size());
+            assertFalse(map.Contains(five));
+            map.remove(zero); // LUCENENET specific - modified, zero is not in 
the set
+            assertEquals(4, map.Count);
+            assertFalse(map.Contains(zero)); // LUCENENET specific - ensure 
zero was not added
+        }
+
+        /// <summary>
+        /// size returns the correct values
+        /// </summary>
+        [Test]
+        public void TestCount()
+        {
+            ConcurrentHashSet<object> map = Map5();
+            // ReSharper disable once CollectionNeverUpdated.Local - indeed, 
that's what we're testing here
+            ConcurrentHashSet<object> empty = new ConcurrentHashSet<object>();
+            assertEquals(0, empty.Count);
+            assertEquals(5, map.Count);
+        }
+
+        // LUCENENET - testToString omitted, could use Collections.ToString 
instead
+
+        /// <summary>
+        /// Cannot create with negative capacity
+        /// </summary>
+        [Test]
+        public void TestConstructor1() {
+            try
+            {
+                _ = new ConcurrentHashSet<object>(8, -1);
+                shouldThrow();
+            }
+            catch (ArgumentOutOfRangeException) // LUCENENET specific - 
changed from IllegalArgumentException to ArgumentOutOfRangeException
+            {
+            }
+        }
+
+        /// <summary>
+        /// Cannot create with negative concurrency level
+        /// </summary>
+        [Test]
+        public void TestConstructor2()
+        {
+            try
+            {
+                _ = new ConcurrentHashSet<object>(-1, 100);
+                shouldThrow();
+            }
+            catch (ArgumentOutOfRangeException) // LUCENENET specific - 
changed from IllegalArgumentException to ArgumentOutOfRangeException
+            {
+            }
+        }
+
+        // LUCENENET - testConstructor3 omitted, we don't have a 
single-int-argument constructor
+        // LUCENENET - omitted *_NullPointerException tests that are not 
relevant
+
+        /// <summary>
+        /// Contains(null) should not throw.
+        /// </summary>
+        /// <remarks>
+        /// This differs from the ConcurrentHashMap tests in that we support 
null values.
+        /// </remarks>
+        [Test, LuceneNetSpecific]
+        public void TestNullSupport()
+        {
+            ConcurrentHashSet<object?> c = new ConcurrentHashSet<object?>
+            {
+                null // implicitly calls Add which should not throw on null
+            };
+            Assert.IsTrue(c.Contains(null));
+            c.Add(null); // should keep set the same
+            Assert.IsTrue(c.Contains(null));
+            Assert.AreEqual(1, c.Count);
+            Assert.IsTrue(c.TryRemove(null));
+            Assert.IsFalse(c.Contains(null));
+            Assert.AreEqual(0, c.Count);
+        }
+
+        // LUCENENET - omitted testSerialization due to lack of serialization 
support
+        // LUCENENET - omitted testSetValueWriteThrough as that is not 
applicable to a set
+
+        // Ported from 
https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/CollectionsTest.java#L1474-L1532
+        /// <summary>
+        /// Apache Harmony test for 
java.util.Collections#synchronizedSet(java.util.Set), adapted for <see 
cref="ConcurrentHashSet{T}"/>.
+        /// </summary>
+        [Test]
+        public void TestSynchronizedSet()
+        {
+            HashSet<object> smallSet = new HashSet<object>();
+            for (int i = 0; i < 50; i++)
+            {
+                smallSet.Add(objArray[i]);
+            }
+
+            const int numberOfLoops = 200;
+            ConcurrentHashSet<object> synchSet = new 
ConcurrentHashSet<object>(smallSet); // was: 
Collections.synchronizedSet(smallSet);
+            // Replacing the previous line with the line below should cause 
the test
+            // to fail--the set below isn't synchronized
+            // ISet<object> synchSet = smallSet;
+
+            SynchCollectionChecker normalSynchChecker = new 
SynchCollectionChecker(
+                synchSet, false, numberOfLoops);
+            SynchCollectionChecker offsetSynchChecker = new 
SynchCollectionChecker(
+                synchSet, true, numberOfLoops);
+            ThreadJob normalThread = normalSynchChecker;
+            ThreadJob offsetThread = offsetSynchChecker;
+            normalThread.Start();
+            offsetThread.Start();
+            while ((normalSynchChecker.NumberOfChecks < numberOfLoops)
+                   || (offsetSynchChecker.NumberOfChecks < numberOfLoops))
+            {
+                try
+                {
+                    Thread.Sleep(10);
+                }
+                catch (Exception e) when (e.IsInterruptedException())
+                {
+                    //Expected
+                }
+            }
+            assertTrue("Returned set corrupted by multiple thread access",
+                normalSynchChecker.Result
+                && offsetSynchChecker.Result);
+            try
+            {
+                normalThread.Join(5000);
+                offsetThread.Join(5000);
+            }
+            catch (Exception e) when (e.IsInterruptedException())
+            {
+                fail("join() interrupted");
+            }
+
+            ISet<object?> mySet = new ConcurrentHashSet<object?>(smallSet); // 
was: Collections.synchronizedSet(smallSet);
+            mySet.Add(null);
+            assertTrue("Trying to use nulls in list failed", 
mySet.Contains(null));
+
+            smallSet = new HashSet<object>();
+            for (int i = 0; i < 100; i++)
+            {
+                smallSet.Add(objArray[i]);
+            }
+            new Support_SetTest(new 
ConcurrentHashSet<int>(smallSet.Cast<int>())) // LUCENENET: add cast to int
+                .RunTest();
+
+            //Test self reference
+            mySet = new ConcurrentHashSet<object?>(smallSet); // was: 
Collections.synchronizedSet(smallSet);
+            mySet.Add(mySet); // LUCENENET specific - references are not the 
same when wrapping via constructor, so adding mySet instead of smallSet
+            assertTrue("should contain self ref", 
Collections.ToString(mySet).IndexOf("(this", StringComparison.Ordinal) > -1);
+        }
     }
 }
diff --git a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs 
b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
index e892d8854..7eefb55b1 100644
--- a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
+++ b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
@@ -381,6 +381,24 @@ public void unexpectedException()
             fail("Unexpected exception");
         }
 
+        protected static readonly int zero = 0;
+        protected static readonly int one = 1;
+        protected static readonly int two = 2;
+        protected static readonly int three  = 3;
+        protected static readonly int four  = 4;
+        protected static readonly int five  = 5;
+        protected static readonly int six = 6;
+        protected static readonly int seven = 7;
+        protected static readonly int eight = 8;
+        protected static readonly int nine = 9;
+        protected static readonly int m1 = -1;
+        protected static readonly int m2 = -2;
+        protected static readonly int m3 = -3;
+        protected static readonly int m4 = -4;
+        protected static readonly int m5 = -5;
+        protected static readonly int m6 = -6;
+        protected static readonly int m10 = -10;
+
         internal void ShortRunnable()
         {
             try
diff --git a/src/Lucene.Net/Support/ConcurrentHashSet.cs 
b/src/Lucene.Net/Support/ConcurrentHashSet.cs
index 877e4685e..fd7e07ae8 100644
--- a/src/Lucene.Net/Support/ConcurrentHashSet.cs
+++ b/src/Lucene.Net/Support/ConcurrentHashSet.cs
@@ -29,7 +29,11 @@ MIT License
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using System.Threading;
+using JCG = J2N.Collections.Generic;
+// ReSharper disable RedundantExtendsListEntry
+#nullable enable
 
 namespace Lucene.Net.Support
 {
@@ -43,6 +47,9 @@ namespace Lucene.Net.Support
     /// </remarks>
     [DebuggerDisplay("Count = {Count}")]
     internal class ConcurrentHashSet<T> : ISet<T>, IReadOnlyCollection<T>, 
ICollection<T>
+#if FEATURE_READONLYSET
+        , IReadOnlySet<T>
+#endif
     {
         private const int DefaultCapacity = 31;
         private const int MaxLockNumber = 1024;
@@ -188,7 +195,7 @@ public ConcurrentHashSet(IEnumerable<T> collection)
         /// </summary>
         /// <param name="comparer">The <see 
cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
         /// implementation to use when comparing items.</param>
-        public ConcurrentHashSet(IEqualityComparer<T> comparer)
+        public ConcurrentHashSet(IEqualityComparer<T>? comparer)
             : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer)
         {
         }
@@ -209,7 +216,7 @@ public ConcurrentHashSet(IEqualityComparer<T> comparer)
         /// <exception cref="ArgumentNullException"><paramref 
name="collection"/> is a null reference
         /// (Nothing in Visual Basic).
         /// </exception>
-        public ConcurrentHashSet(IEnumerable<T> collection, 
IEqualityComparer<T> comparer)
+        public ConcurrentHashSet(IEnumerable<T> collection, 
IEqualityComparer<T>? comparer)
             : this(comparer)
         {
             if (collection is null) throw new 
ArgumentNullException(nameof(collection));
@@ -265,7 +272,7 @@ public ConcurrentHashSet(int concurrencyLevel, int 
capacity, IEqualityComparer<T
         {
         }
 
-        private ConcurrentHashSet(int concurrencyLevel, int capacity, bool 
growLockArray, IEqualityComparer<T> comparer)
+        private ConcurrentHashSet(int concurrencyLevel, int capacity, bool 
growLockArray, IEqualityComparer<T>? comparer)
         {
             if (concurrencyLevel < 1) throw new 
ArgumentOutOfRangeException(nameof(concurrencyLevel));
             if (capacity < 0) throw new 
ArgumentOutOfRangeException(nameof(capacity));
@@ -289,7 +296,7 @@ private ConcurrentHashSet(int concurrencyLevel, int 
capacity, bool growLockArray
 
             _growLockArray = growLockArray;
             _budget = buckets.Length / locks.Length;
-            _comparer = comparer ?? EqualityComparer<T>.Default;
+            _comparer = comparer ?? JCG.EqualityComparer<T>.Default;
         }
 
         /// <summary>
@@ -300,8 +307,8 @@ private ConcurrentHashSet(int concurrencyLevel, int 
capacity, bool growLockArray
         /// successfully; false if it already exists.</returns>
         /// <exception cref="T:OverflowException">The <see 
cref="ConcurrentHashSet{T}"/>
         /// contains too many items.</exception>
-        public bool Add(T item) =>
-            AddInternal(item, _comparer.GetHashCode(item), true);
+        public bool Add(T item)
+            => AddInternal(item, GetItemHashCode(item), true);
 
         /// <summary>
         /// Removes all items from the <see cref="ConcurrentHashSet{T}"/>.
@@ -336,7 +343,7 @@ private void ClearInternal()
         /// <returns>true if the <see cref="ConcurrentHashSet{T}"/> contains 
the item; otherwise, false.</returns>
         public bool Contains(T item)
         {
-            var hashcode = _comparer.GetHashCode(item);
+            var hashcode = GetItemHashCode(item);
 
             // We must capture the _buckets field in a local variable. It is 
set to a new table on each table resize.
             var tables = _tables;
@@ -366,7 +373,7 @@ public bool Contains(T item)
         /// <returns>true if an item was removed successfully; otherwise, 
false.</returns>
         public bool TryRemove(T item)
         {
-            var hashcode = _comparer.GetHashCode(item);
+            var hashcode = GetItemHashCode(item);
             return TryRemoveInternal(item, hashcode, acquireLock: true);
         }
 
@@ -393,10 +400,10 @@ private bool TryRemoveInternal(T item, int hashcode, bool 
acquireLock)
                         continue;
                     }
 
-                    Node previous = null;
+                    Node? previous = null;
                     for (var current = tables.Buckets[bucketNo]; current != 
null; current = current.Next)
                     {
-                        Debug.Assert((previous is null && current == 
tables.Buckets[bucketNo]) || previous.Next == current);
+                        Debug.Assert((previous is null && current == 
tables.Buckets[bucketNo]) || previous?.Next == current);
 
                         if (hashcode == current.Hashcode && 
_comparer.Equals(current.Item, item))
                         {
@@ -493,7 +500,7 @@ private void InitializeFromCollection(IEnumerable<T> 
collection)
         {
             foreach (var item in collection)
             {
-                AddInternal(item, _comparer.GetHashCode(item), false);
+                AddInternal(item, GetItemHashCode(item), false);
             }
 
             if (_budget == 0)
@@ -525,10 +532,10 @@ private bool AddInternal(T item, int hashcode, bool 
acquireLock)
                     }
 
                     // Try to find this item in the bucket
-                    Node previous = null;
+                    Node? previous = null;
                     for (var current = tables.Buckets[bucketNo]; current != 
null; current = current.Next)
                     {
-                        Debug.Assert(previous is null && current == 
tables.Buckets[bucketNo] || previous.Next == current);
+                        Debug.Assert(previous is null && current == 
tables.Buckets[bucketNo] || previous?.Next == current);
                         if (hashcode == current.Hashcode && 
_comparer.Equals(current.Item, item))
                         {
                             return false;
@@ -804,7 +811,7 @@ public void ExceptWith(IEnumerable<T> other)
 
                 foreach (var item in other)
                 {
-                    TryRemoveInternal(item, _comparer.GetHashCode(item), 
acquireLock: false);
+                    TryRemoveInternal(item, GetItemHashCode(item), 
acquireLock: false);
                 }
             }
             finally
@@ -815,7 +822,38 @@ public void ExceptWith(IEnumerable<T> other)
 
         public void IntersectWith(IEnumerable<T> other)
         {
-            throw new NotImplementedException();
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            var locksAcquired = 0;
+            try
+            {
+                AcquireAllLocks(ref locksAcquired);
+
+                if (CountInternal == 0)
+                {
+                    return;
+                }
+
+                if (ReferenceEquals(this, other))
+                {
+                    return;
+                }
+
+                var otherSet = new HashSet<T>(other, _comparer);
+
+                foreach (var item in this)
+                {
+                    if (!otherSet.Contains(item))
+                    {
+                        TryRemoveInternal(item, GetItemHashCode(item), 
acquireLock: false);
+                    }
+                }
+            }
+            finally
+            {
+                ReleaseLocks(0, locksAcquired);
+            }
         }
 
         public bool IsProperSubsetOf(IEnumerable<T> other)
@@ -835,7 +873,28 @@ public bool IsSubsetOf(IEnumerable<T> other)
 
         public bool IsSupersetOf(IEnumerable<T> other)
         {
-            throw new NotImplementedException();
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            var locksAcquired = 0;
+            try
+            {
+                AcquireAllLocks(ref locksAcquired);
+
+                foreach (var item in other)
+                {
+                    if (!Contains(item))
+                    {
+                        return false;
+                    }
+                }
+            }
+            finally
+            {
+                ReleaseLocks(0, locksAcquired);
+            }
+
+            return true;
         }
 
         public bool Overlaps(IEnumerable<T> other)
@@ -864,7 +923,7 @@ public void UnionWith(IEnumerable<T> other)
                 AcquireAllLocks(ref locksAcquired);
 
                 foreach (var item in other)
-                    AddInternal(item, _comparer.GetHashCode(item), 
acquireLock: false);
+                    AddInternal(item, GetItemHashCode(item), acquireLock: 
false);
             }
             finally
             {
@@ -872,14 +931,17 @@ public void UnionWith(IEnumerable<T> other)
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private int GetItemHashCode(T item) => item is not null ? 
_comparer.GetHashCode(item) : 0;
+
         private class Tables
         {
-            public readonly Node[] Buckets;
+            public readonly Node?[] Buckets;
             public readonly object[] Locks;
 
             public volatile int[] CountPerLock;
 
-            public Tables(Node[] buckets, object[] locks, int[] countPerLock)
+            public Tables(Node?[] buckets, object[] locks, int[] countPerLock)
             {
                 Buckets = buckets;
                 Locks = locks;
@@ -892,9 +954,9 @@ private class Node
             public readonly T Item;
             public readonly int Hashcode;
 
-            public volatile Node Next;
+            public volatile Node? Next;
 
-            public Node(T item, int hashcode, Node next)
+            public Node(T item, int hashcode, Node? next)
             {
                 Item = item;
                 Hashcode = hashcode;


Reply via email to