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;