| // 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; |
| using JCG = J2N.Collections.Generic; |
| #nullable enable |
| |
| 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. |
| */ |
| |
| /// <summary> |
| /// Base class for tests for <see cref="TestConcurrentHashSet"/> and <see cref="TestConcurrentSet"/>. |
| /// </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}"/> and <see cref="ConcurrentSet{T}"/>. |
| /// </remarks> |
| public abstract class BaseConcurrentSetTestCase : JSR166TestCase |
| { |
| |
| #region Factory Methods |
| |
| /// <summary> |
| /// Creates a new instance of a set type. |
| /// </summary> |
| /// <typeparam name="T">The type of items in the set.</typeparam> |
| /// <returns>Returns a new instance of the set type.</returns> |
| protected abstract ISet<T> NewSet<T>(); |
| |
| /// <summary> |
| /// Creates a new instance of a set type with the specified wrapped set. |
| /// </summary> |
| /// <param name="set">The set to wrap.</param> |
| /// <typeparam name="T">The type of items in the set.</typeparam> |
| /// <returns>Returns a new instance of the set type with the specified wrapped set.</returns> |
| protected abstract ISet<T> NewSet<T>(ISet<T> set); |
| |
| |
| |
| #endregion |
| |
| /// <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.Count; |
| 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() |
| { |
| // Numbers 0-8, 10-80, 99 |
| var initialSet = Enumerable.Range(1, 8) |
| .Concat(Enumerable.Range(1, 8).Select(i => i * 10)) |
| .Append(99) |
| .Append(0) |
| .ToHashSet(); |
| |
| var hashSet = NewSet(initialSet); |
| |
| Parallel.ForEach(Enumerable.Range(1, 8), i => |
| { |
| // Remove i and i * 10, i.e. 1 and 10, 2 and 20, etc. |
| var except = new[] { i, i * 10 }; |
| hashSet.ExceptWith(except); |
| }); |
| |
| Assert.AreEqual(2, hashSet.Count); |
| 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 ISet<object> Map5() |
| { |
| ISet<object> map = NewSet<object>(); |
| assertTrue(map.Count == 0); |
| map.Add(one); |
| map.Add(two); |
| map.Add(three); |
| map.Add(four); |
| map.Add(five); |
| assertFalse(map.Count == 0); |
| assertEquals(5, map.Count); |
| return map; |
| } |
| |
| /// <summary> |
| /// clear removes all items |
| /// </summary> |
| [Test] |
| public void TestClear() |
| { |
| ISet<object> map = Map5(); |
| map.Clear(); |
| assertEquals(map.Count, 0); |
| } |
| |
| /// <summary> |
| /// Sets with same contents are equal |
| /// </summary> |
| [Test] |
| public virtual void TestEquals() |
| { |
| ISet<object> map1 = Map5(); |
| ISet<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() |
| { |
| ISet<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() |
| { |
| ISet<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() |
| { |
| ISet<object> empty = NewSet<object>(); |
| ISet<object> map = Map5(); |
| assertTrue(empty.Count == 0); |
| assertFalse(map.Count == 0); |
| } |
| |
| // 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() |
| { |
| ISet<object> empty = NewSet<object>(); |
| ISet<object> map = Map5(); |
| empty.UnionWith(map); |
| assertEquals(5, empty.Count); |
| 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() |
| { |
| ISet<object> map = Map5(); |
| map.Remove(five); |
| assertEquals(4, map.Count); |
| assertFalse(map.Contains(five)); |
| } |
| |
| /// <summary> |
| /// remove(value) removes only if value present |
| /// </summary> |
| [Test] |
| public void TestRemove2() |
| { |
| ISet<object> map = Map5(); |
| map.Remove(five); |
| assertEquals(4, map.Count); |
| 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() |
| { |
| ISet<object> map = Map5(); |
| // ReSharper disable once CollectionNeverUpdated.Local - indeed, that's what we're testing here |
| ISet<object> empty = NewSet<object>(); |
| assertEquals(0, empty.Count); |
| assertEquals(5, map.Count); |
| } |
| |
| // LUCENENET - testToString omitted, could use Collections.ToString instead |
| |
| // LUCENENET - TestConstructor methods moved to TestConcurrentHashSet class |
| |
| // 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() |
| { |
| ISet<object?> c = NewSet<object?>(); |
| c.Add(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.Remove(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}"/> and <see cref="ConcurrentSet{T}"/>, |
| /// using <see cref="HashSet{T}"/> as the inner set. |
| /// </summary> |
| [Test] |
| public void TestSynchronizedSet() |
| { |
| BaseTestSynchronizedSet(() => new HashSet<object?>()); |
| } |
| |
| /// <summary> |
| /// Runs the synchronized set tests using <see cref="J2N.Collections.Generic.LinkedHashSet{T}"/> as the inner set type. |
| /// </summary> |
| [Test] |
| public void TestSynchronizedSet_LinkedHashSet() |
| { |
| BaseTestSynchronizedSet(() => new JCG.LinkedHashSet<object?>()); |
| } |
| |
| protected void BaseTestSynchronizedSet(Func<ISet<object?>> innerSetFactory) |
| { |
| ISet<object?> smallSet = innerSetFactory(); |
| for (int i = 0; i < 50; i++) |
| { |
| smallSet.Add(objArray[i]); |
| } |
| |
| const int numberOfLoops = 200; |
| ISet<object?> synchSet = NewSet(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 = NewSet(smallSet); // was: Collections.synchronizedSet(smallSet); |
| mySet.Add(null); |
| assertTrue("Trying to use nulls in list failed", mySet.Contains(null)); |
| |
| smallSet = innerSetFactory(); |
| for (int i = 0; i < 100; i++) |
| { |
| smallSet.Add(objArray[i]); |
| } |
| new Support_SetTest(NewSet(smallSet.Cast<int>().ToHashSet())) // LUCENENET: add cast to int |
| .RunTest(); |
| |
| //Test self reference |
| mySet = NewSet(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); |
| } |
| } |
| } |