blob: 17de4d16ad4ae2c084d4f831a2ae5596b0f6517e [file] [log] [blame]
// From Apache Harmony tests:
// https://github.com/apache/harmony/blob/trunk/classlib/modules/concurrent/src/test/java/JSR166TestCase.java
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using ThreadInterruptedException = System.Threading.ThreadInterruptedException;
#nullable enable
namespace Lucene.Net.Support.Threading
{
/*
* 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>
/// LUCENENET NOTE: This class has been adapted from the Apache Harmony
/// tests. The original javadoc is included below, and adapted where necessary.
/// <para />
///
/// Base class for JSR166 Junit TCK tests. Defines some constants,
/// utility methods and classes, as well as a simple framework for
/// helping to make sure that assertions failing in generated threads
/// cause the associated test that generated them to itself fail (which
/// JUnit does not otherwise arrange). The rules for creating such
/// tests are:
///
/// <list type="bullets">
///
/// <item> All assertions in code running in generated threads must use
/// the forms <see cref="threadFail"/>, <see cref="threadAssertTrue"/>,
/// <see cref="threadAssertEquals(long,long)"/>, <see cref="threadAssertEquals(object,object)"/>
/// or <see cref="threadAssertNull"/>, (not
/// <c>fail</c>, <c>assertTrue</c>, etc.) It is OK (but not
/// particularly recommended) for other code to use these forms too.
/// Only the most typically used JUnit assertion methods are defined
/// this way, but enough to live with.</item>
///
/// <item> If you override <see cref="SetUp"/> or <see cref="TearDown"/>, make sure
/// to invoke <c>base.SetUp</c> and <c>base.TearDown</c> within
/// them. These methods are used to clear and check for thread
/// assertion failures.</item>
///
/// <item>All delays and timeouts must use one of the constants
/// <see cref="SHORT_DELAY_MS"/>, <see cref="SMALL_DELAY_MS"/>, <see cref="MEDIUM_DELAY_MS"/>,
/// <see cref="LONG_DELAY_MS"/>. The idea here is that a SHORT is always
/// discriminable from zero time, and always allows enough time for the
/// small amounts of computation (creating a thread, calling a few
/// methods, etc) needed to reach a timeout point. Similarly, a SMALL
/// is always discriminable as larger than SHORT and smaller than
/// MEDIUM. And so on. These constants are set to conservative values,
/// but even so, if there is ever any doubt, they can all be increased
/// in one spot to rerun tests on slower platforms.</item>
///
/// <item> All threads generated must be joined inside each test case
/// method (or <c>fail</c> to do so) before returning from the
/// method. The <see cref="joinPool"/> method can be used to do this when
/// using Executors.</item>
///
/// </list>
///
/// <para />
/// <b>Other notes</b>
/// <list type="bullet">
///
/// <item> Usually, there is one testcase method per JSR166 method
/// covering "normal" operation, and then as many exception-testing
/// methods as there are exceptions the method can throw. Sometimes
/// there are multiple tests per JSR166 method when the different
/// "normal" behaviors differ significantly. And sometimes testcases
/// cover multiple methods when they cannot be tested in
/// isolation.</item>
///
/// <item> The documentation style for testcases is to provide as javadoc
/// a simple sentence or two describing the property that the testcase
/// method purports to test. The javadocs do not say anything about how
/// the property is tested. To find out, read the code.</item>
///
/// <item> These tests are "conformance tests", and do not attempt to
/// test throughput, latency, scalability or other performance factors
/// (see the separate "jtreg" tests for a set intended to check these
/// for the most central aspects of functionality.) So, most tests use
/// the smallest sensible numbers of threads, collection sizes, etc
/// needed to check basic conformance.</item>
///
/// <item>The test classes currently do not declare inclusion in
/// any particular package to simplify things for people integrating
/// them in TCK test suites.</item>
///
/// <!-- LUCENENET: not implemented
/// <item> As a convenience, the <c>main</c> of this class (JSR166TestCase)
/// runs all JSR166 unit tests.</item>
/// -->
///
/// </list>
/// </summary>
public class JSR166TestCase : LuceneTestCase
{
// /**
// * Runs all JSR166 unit tests using junit.textui.TestRunner
// */
//public static void main(String[] args)
//{
// int iters = 1;
// if (args.length > 0)
// iters = Integer.parseInt(args[0]);
// Test s = suite();
// for (int i = 0; i < iters; ++i)
// {
// junit.textui.TestRunner.run(s);
// System.gc();
// System.runFinalization();
// }
// System.exit(0);
//}
///**
// * Collects all JSR166 unit tests as one suite
// */
//public static Test suite()
//{
// TestSuite suite = new TestSuite("JSR166 Unit Tests");
// suite.addTest(new TestSuite(AbstractExecutorServiceTest.class));
// suite.addTest(new TestSuite(AbstractQueueTest.class));
// suite.addTest(new TestSuite(AbstractQueuedSynchronizerTest.class));
// suite.addTest(new TestSuite(ArrayBlockingQueueTest.class));
// suite.addTest(new TestSuite(AtomicBooleanTest.class));
// suite.addTest(new TestSuite(AtomicIntegerArrayTest.class));
// suite.addTest(new TestSuite(AtomicIntegerFieldUpdaterTest.class));
// suite.addTest(new TestSuite(AtomicIntegerTest.class));
// suite.addTest(new TestSuite(AtomicLongArrayTest.class));
// suite.addTest(new TestSuite(AtomicLongFieldUpdaterTest.class));
// suite.addTest(new TestSuite(AtomicLongTest.class));
// suite.addTest(new TestSuite(AtomicMarkableReferenceTest.class));
// suite.addTest(new TestSuite(AtomicReferenceArrayTest.class));
// suite.addTest(new TestSuite(AtomicReferenceFieldUpdaterTest.class));
// suite.addTest(new TestSuite(AtomicReferenceTest.class));
// suite.addTest(new TestSuite(AtomicStampedReferenceTest.class));
// suite.addTest(new TestSuite(ConcurrentHashMapTest.class));
// suite.addTest(new TestSuite(ConcurrentLinkedQueueTest.class));
// suite.addTest(new TestSuite(CopyOnWriteArrayListTest.class));
// suite.addTest(new TestSuite(CopyOnWriteArraySetTest.class));
// suite.addTest(new TestSuite(CountDownLatchTest.class));
// suite.addTest(new TestSuite(CyclicBarrierTest.class));
// suite.addTest(new TestSuite(DelayQueueTest.class));
// suite.addTest(new TestSuite(ExchangerTest.class));
// suite.addTest(new TestSuite(ExecutorsTest.class));
// suite.addTest(new TestSuite(ExecutorCompletionServiceTest.class));
// suite.addTest(new TestSuite(FutureTaskTest.class));
// suite.addTest(new TestSuite(LinkedBlockingQueueTest.class));
// suite.addTest(new TestSuite(LinkedListTest.class));
// suite.addTest(new TestSuite(LockSupportTest.class));
// suite.addTest(new TestSuite(PriorityBlockingQueueTest.class));
// suite.addTest(new TestSuite(PriorityQueueTest.class));
// suite.addTest(new TestSuite(ReentrantLockTest.class));
// suite.addTest(new TestSuite(ReentrantReadWriteLockTest.class));
// suite.addTest(new TestSuite(ScheduledExecutorTest.class));
// suite.addTest(new TestSuite(SemaphoreTest.class));
// suite.addTest(new TestSuite(SynchronousQueueTest.class));
// suite.addTest(new TestSuite(SystemTest.class));
// suite.addTest(new TestSuite(ThreadLocalTest.class));
// suite.addTest(new TestSuite(ThreadPoolExecutorTest.class));
// suite.addTest(new TestSuite(ThreadTest.class));
// suite.addTest(new TestSuite(TimeUnitTest.class));
// return suite;
//}
public static int SHORT_DELAY_MS;
public static int SMALL_DELAY_MS;
public static int MEDIUM_DELAY_MS;
public static int LONG_DELAY_MS;
/**
* Returns the shortest timed delay. This could
* be reimplemented to use for example a Property.
*/
protected int getShortDelay()
{
return 50;
}
/**
* Sets delays as multiples of SHORT_DELAY.
*/
protected void setDelays()
{
SHORT_DELAY_MS = getShortDelay();
SMALL_DELAY_MS = SHORT_DELAY_MS * 5;
MEDIUM_DELAY_MS = SHORT_DELAY_MS * 10;
LONG_DELAY_MS = SHORT_DELAY_MS * 50;
}
/**
* Flag set true if any threadAssert methods fail
*/
internal volatile bool threadFailed;
/**
* Initializes test to indicate that no thread assertions have failed
*/
public override void SetUp()
{
base.SetUp();
setDelays();
threadFailed = false;
}
/**
* Triggers test case failure if any thread assertions have failed
*/
public override void TearDown()
{
assertFalse(threadFailed);
base.TearDown();
}
/**
* Fail, also setting status to indicate current testcase should fail
*/
public void threadFail(string reason)
{
threadFailed = true;
fail(reason);
}
/**
* If expression not true, set status to indicate current testcase
* should fail
*/
public void threadAssertTrue(bool b)
{
if (!b)
{
threadFailed = true;
assertTrue(b);
}
}
/**
* If expression not false, set status to indicate current testcase
* should fail
*/
public void threadAssertFalse(bool b)
{
if (b)
{
threadFailed = true;
assertFalse(b);
}
}
/**
* If argument not null, set status to indicate current testcase
* should fail
*/
public void threadAssertNull(object? x)
{
if (x != null)
{
threadFailed = true;
assertNull(x);
}
}
/**
* If arguments not equal, set status to indicate current testcase
* should fail
*/
public void threadAssertEquals(long x, long y)
{
if (x != y)
{
threadFailed = true;
assertEquals(x, y);
}
}
/**
* If arguments not equal, set status to indicate current testcase
* should fail
*/
public void threadAssertEquals(object? x, object? y)
{
if (x != y && (x == null || !x.equals(y)))
{
threadFailed = true;
assertEquals(x, y);
}
}
/**
* threadFail with message "should throw exception"
*/
public void threadShouldThrow()
{
//try
//{
threadFailed = true;
fail("should throw exception");
//}
//catch (AssertionFailedError e)
//{
// e.printStackTrace();
// throw e;
//}
}
/**
* threadFail with message "Unexpected exception"
*/
public void threadUnexpectedException()
{
threadFailed = true;
fail("Unexpected exception");
}
/**
* threadFail with message "Unexpected exception", with argument
*/
public void threadUnexpectedException(Exception ex)
{
threadFailed = true;
ex.PrintStackTrace();
fail("Unexpected exception: " + ex);
}
/**
* Wait out termination of a thread pool or fail doing so
*/
public void joinPool(TaskScheduler exec)
{
try
{
exec.Shutdown();
assertTrue(exec.AwaitTermination(TimeSpan.FromMilliseconds(LONG_DELAY_MS)));
}
// catch (SecurityException ok) // LUCENENET - not needed
// {
// // Allowed in case test doesn't have privs
// }
catch (ThreadInterruptedException /*ie*/)
{
fail("Unexpected exception");
}
}
/**
* fail with message "should throw exception"
*/
public void shouldThrow()
{
fail("Should throw exception");
}
/**
* fail with message "Unexpected exception"
*/
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
{
Thread.Sleep(SHORT_DELAY_MS);
}
catch (Exception e)
{
threadUnexpectedException(e);
}
}
internal void MediumRunnable()
{
try
{
Thread.Sleep(MEDIUM_DELAY_MS);
}
catch (Exception e)
{
threadUnexpectedException(e);
}
}
// LUCENENET TODO: Complete port
}
/// <summary>
/// LUCENENET specific - fake support for an API that feels like ThreadPoolExecutor.
/// </summary>
internal static class JSR166TestCaseExtensions
{
/// <summary>
/// LUCENENET specific - state to keep track of tasks.
/// <see cref="LimitedConcurrencyLevelTaskScheduler"/> removes tasks from the list when they complete,
/// so this class is needed to keep track of them.
/// </summary>
private class TaskState
{
private readonly TaskFactory _factory;
private readonly List<Task> _tasks = new();
public TaskState(TaskScheduler scheduler)
{
_factory = new TaskFactory(scheduler);
}
public void NewTask(Action action)
{
var task = _factory.StartNew(action);
_tasks.Add(task);
}
public int ActiveCount => _tasks.Count(t => t.Status == TaskStatus.Running);
public int CompletedCount => _tasks.Count(t => t.IsCompleted);
public int TaskCount => _tasks.Count;
public bool AllCompleted => _tasks.All(t => t.IsCompleted);
public bool JoinAll(TimeSpan timeout) => Task.WhenAll(_tasks).Wait(timeout);
}
private static readonly ConditionalWeakTable<TaskScheduler, TaskState> _taskFactories = new();
public static void Execute(this TaskScheduler scheduler, Action action)
{
if (!_taskFactories.TryGetValue(scheduler, out TaskState? state))
{
state = new TaskState(scheduler);
_taskFactories.Add(scheduler, state);
}
state.NewTask(action);
}
public static bool AwaitTermination(this TaskScheduler scheduler, TimeSpan timeout)
{
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
{
return state.JoinAll(timeout);
}
return true;
}
public static int GetActiveCount(this TaskScheduler scheduler)
{
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
{
// Approximate the number of running threads, which shouldn't exceed the concurrency level
return Math.Min(scheduler.MaximumConcurrencyLevel, state.ActiveCount);
}
return 0;
}
public static int GetCompletedTaskCount(this TaskScheduler scheduler)
{
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
{
return state.CompletedCount;
}
return 0;
}
public static int GetTaskCount(this TaskScheduler scheduler)
{
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
{
return state.TaskCount;
}
return 0;
}
public static void Shutdown(this TaskScheduler scheduler)
{
if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl)
{
lcl.Shutdown();
}
}
public static bool IsTerminated(this TaskScheduler scheduler)
{
if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl
&& _taskFactories.TryGetValue(scheduler, out TaskState? state))
{
return lcl.IsShutdown && state.AllCompleted;
}
return false; // can't be shut down, so can't be terminated
}
}
}