blob: 46ec2ef77cccc86f87e9d186f2c3a691bcd3695b [file] [log] [blame]
// 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);
}
}
}