Skip to content

Commit b1d309f

Browse files
authoredJan 22, 2025··
Add unit tests for LimitedConcurrencyLevelTaskScheduler, #1110 (#1119)
* Add unit tests for LimitedConcurrencyLevelTaskScheduler, #1110 * Move TaskState into Extensions class and make private, #1110
1 parent 5007427 commit b1d309f

File tree

4 files changed

+535
-145
lines changed

4 files changed

+535
-145
lines changed
 

‎Directory.Build.targets

-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@
112112
<DefineConstants>$(DefineConstants);FEATURE_ASSEMBLY_GETCALLINGASSEMBLY</DefineConstants>
113113
<DefineConstants>$(DefineConstants);FEATURE_FILESTREAM_LOCK</DefineConstants>
114114
<DefineConstants>$(DefineConstants);FEATURE_TEXTWRITER_CLOSE</DefineConstants>
115-
<DefineConstants>$(DefineConstants);FEATURE_THREADPOOL_UNSAFEQUEUEWORKITEM</DefineConstants>
116115
<DefineConstants>$(DefineConstants);FEATURE_TYPE_GETMETHOD__BINDINGFLAGS_PARAMS</DefineConstants>
117116

118117
</PropertyGroup>

‎src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs

+248-96
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
using Lucene.Net.Util;
1+
// From Apache Harmony tests:
2+
// https://github.com/apache/harmony/blob/trunk/classlib/modules/concurrent/src/test/java/JSR166TestCase.java
3+
using Lucene.Net.Util;
24
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using ThreadInterruptedException = System.Threading.ThreadInterruptedException;
11+
12+
#nullable enable
313

414
namespace Lucene.Net.Support.Threading
515
{
@@ -20,82 +30,90 @@ namespace Lucene.Net.Support.Threading
2030
* limitations under the License.
2131
*/
2232

23-
/**
24-
* Base class for JSR166 Junit TCK tests. Defines some constants,
25-
* utility methods and classes, as well as a simple framework for
26-
* helping to make sure that assertions failing in generated threads
27-
* cause the associated test that generated them to itself fail (which
28-
* JUnit does not otherwise arrange). The rules for creating such
29-
* tests are:
30-
*
31-
* <ol>
32-
*
33-
* <li> All assertions in code running in generated threads must use
34-
* the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link
35-
* #threadAssertEquals}, or {@link #threadAssertNull}, (not
36-
* <tt>fail</tt>, <tt>assertTrue</tt>, etc.) It is OK (but not
37-
* particularly recommended) for other code to use these forms too.
38-
* Only the most typically used JUnit assertion methods are defined
39-
* this way, but enough to live with.</li>
40-
*
41-
* <li> If you override {@link #setUp} or {@link #tearDown}, make sure
42-
* to invoke <tt>super.setUp</tt> and <tt>super.tearDown</tt> within
43-
* them. These methods are used to clear and check for thread
44-
* assertion failures.</li>
45-
*
46-
* <li>All delays and timeouts must use one of the constants <tt>
47-
* SHORT_DELAY_MS</tt>, <tt> SMALL_DELAY_MS</tt>, <tt> MEDIUM_DELAY_MS</tt>,
48-
* <tt> LONG_DELAY_MS</tt>. The idea here is that a SHORT is always
49-
* discriminable from zero time, and always allows enough time for the
50-
* small amounts of computation (creating a thread, calling a few
51-
* methods, etc) needed to reach a timeout point. Similarly, a SMALL
52-
* is always discriminable as larger than SHORT and smaller than
53-
* MEDIUM. And so on. These constants are set to conservative values,
54-
* but even so, if there is ever any doubt, they can all be increased
55-
* in one spot to rerun tests on slower platforms.</li>
56-
*
57-
* <li> All threads generated must be joined inside each test case
58-
* method (or <tt>fail</tt> to do so) before returning from the
59-
* method. The <tt> joinPool</tt> method can be used to do this when
60-
* using Executors.</li>
61-
*
62-
* </ol>
63-
*
64-
* <p> <b>Other notes</b>
65-
* <ul>
66-
*
67-
* <li> Usually, there is one testcase method per JSR166 method
68-
* covering "normal" operation, and then as many exception-testing
69-
* methods as there are exceptions the method can throw. Sometimes
70-
* there are multiple tests per JSR166 method when the different
71-
* "normal" behaviors differ significantly. And sometimes testcases
72-
* cover multiple methods when they cannot be tested in
73-
* isolation.</li>
74-
*
75-
* <li> The documentation style for testcases is to provide as javadoc
76-
* a simple sentence or two describing the property that the testcase
77-
* method purports to test. The javadocs do not say anything about how
78-
* the property is tested. To find out, read the code.</li>
79-
*
80-
* <li> These tests are "conformance tests", and do not attempt to
81-
* test throughput, latency, scalability or other performance factors
82-
* (see the separate "jtreg" tests for a set intended to check these
83-
* for the most central aspects of functionality.) So, most tests use
84-
* the smallest sensible numbers of threads, collection sizes, etc
85-
* needed to check basic conformance.</li>
86-
*
87-
* <li>The test classes currently do not declare inclusion in
88-
* any particular package to simplify things for people integrating
89-
* them in TCK test suites.</li>
90-
*
91-
* <li> As a convenience, the <tt>main</tt> of this class (JSR166TestCase)
92-
* runs all JSR166 unit tests.</li>
93-
*
94-
* </ul>
95-
*/
33+
/// <summary>
34+
/// LUCENENET NOTE: This class has been adapted from the Apache Harmony
35+
/// tests. The original javadoc is included below, and adapted where necessary.
36+
/// <para />
37+
///
38+
/// Base class for JSR166 Junit TCK tests. Defines some constants,
39+
/// utility methods and classes, as well as a simple framework for
40+
/// helping to make sure that assertions failing in generated threads
41+
/// cause the associated test that generated them to itself fail (which
42+
/// JUnit does not otherwise arrange). The rules for creating such
43+
/// tests are:
44+
///
45+
/// <list type="bullets">
46+
///
47+
/// <item> All assertions in code running in generated threads must use
48+
/// the forms <see cref="threadFail"/>, <see cref="threadAssertTrue"/>,
49+
/// <see cref="threadAssertEquals(long,long)"/>, <see cref="threadAssertEquals(object,object)"/>
50+
/// or <see cref="threadAssertNull"/>, (not
51+
/// <c>fail</c>, <c>assertTrue</c>, etc.) It is OK (but not
52+
/// particularly recommended) for other code to use these forms too.
53+
/// Only the most typically used JUnit assertion methods are defined
54+
/// this way, but enough to live with.</item>
55+
///
56+
/// <item> If you override <see cref="SetUp"/> or <see cref="TearDown"/>, make sure
57+
/// to invoke <c>base.SetUp</c> and <c>base.TearDown</c> within
58+
/// them. These methods are used to clear and check for thread
59+
/// assertion failures.</item>
60+
///
61+
/// <item>All delays and timeouts must use one of the constants
62+
/// <see cref="SHORT_DELAY_MS"/>, <see cref="SMALL_DELAY_MS"/>, <see cref="MEDIUM_DELAY_MS"/>,
63+
/// <see cref="LONG_DELAY_MS"/>. The idea here is that a SHORT is always
64+
/// discriminable from zero time, and always allows enough time for the
65+
/// small amounts of computation (creating a thread, calling a few
66+
/// methods, etc) needed to reach a timeout point. Similarly, a SMALL
67+
/// is always discriminable as larger than SHORT and smaller than
68+
/// MEDIUM. And so on. These constants are set to conservative values,
69+
/// but even so, if there is ever any doubt, they can all be increased
70+
/// in one spot to rerun tests on slower platforms.</item>
71+
///
72+
/// <item> All threads generated must be joined inside each test case
73+
/// method (or <c>fail</c> to do so) before returning from the
74+
/// method. The <see cref="joinPool"/> method can be used to do this when
75+
/// using Executors.</item>
76+
///
77+
/// </list>
78+
///
79+
/// <para />
80+
/// <b>Other notes</b>
81+
/// <list type="bullet">
82+
///
83+
/// <item> Usually, there is one testcase method per JSR166 method
84+
/// covering "normal" operation, and then as many exception-testing
85+
/// methods as there are exceptions the method can throw. Sometimes
86+
/// there are multiple tests per JSR166 method when the different
87+
/// "normal" behaviors differ significantly. And sometimes testcases
88+
/// cover multiple methods when they cannot be tested in
89+
/// isolation.</item>
90+
///
91+
/// <item> The documentation style for testcases is to provide as javadoc
92+
/// a simple sentence or two describing the property that the testcase
93+
/// method purports to test. The javadocs do not say anything about how
94+
/// the property is tested. To find out, read the code.</item>
95+
///
96+
/// <item> These tests are "conformance tests", and do not attempt to
97+
/// test throughput, latency, scalability or other performance factors
98+
/// (see the separate "jtreg" tests for a set intended to check these
99+
/// for the most central aspects of functionality.) So, most tests use
100+
/// the smallest sensible numbers of threads, collection sizes, etc
101+
/// needed to check basic conformance.</item>
102+
///
103+
/// <item>The test classes currently do not declare inclusion in
104+
/// any particular package to simplify things for people integrating
105+
/// them in TCK test suites.</item>
106+
///
107+
/// <!-- LUCENENET: not implemented
108+
/// <item> As a convenience, the <c>main</c> of this class (JSR166TestCase)
109+
/// runs all JSR166 unit tests.</item>
110+
/// -->
111+
///
112+
/// </list>
113+
/// </summary>
96114
public class JSR166TestCase : LuceneTestCase
97115
{
98-
///**
116+
// /**
99117
// * Runs all JSR166 unit tests using junit.textui.TestRunner
100118
// */
101119
//public static void main(String[] args)
@@ -255,7 +273,7 @@ public void threadAssertFalse(bool b)
255273
* If argument not null, set status to indicate current testcase
256274
* should fail
257275
*/
258-
public void threadAssertNull(object x)
276+
public void threadAssertNull(object? x)
259277
{
260278
if (x != null)
261279
{
@@ -281,7 +299,7 @@ public void threadAssertEquals(long x, long y)
281299
* If arguments not equal, set status to indicate current testcase
282300
* should fail
283301
*/
284-
public void threadAssertEquals(object x, object y)
302+
public void threadAssertEquals(object? x, object? y)
285303
{
286304
if (x != y && (x == null || !x.equals(y)))
287305
{
@@ -326,25 +344,25 @@ public void threadUnexpectedException(Exception ex)
326344
fail("Unexpected exception: " + ex);
327345
}
328346

329-
///**
330-
// * Wait out termination of a thread pool or fail doing so
331-
// */
332-
//public void joinPool(ExecutorService exec)
333-
//{
334-
// try
335-
// {
336-
// exec.shutdown();
337-
// assertTrue(exec.awaitTermination(LONG_DELAY_MS, TimeUnit.MILLISECONDS));
338-
// }
339-
// catch (SecurityException ok)
340-
// {
341-
// // Allowed in case test doesn't have privs
342-
// }
343-
// catch (InterruptedException ie)
344-
// {
345-
// fail("Unexpected exception");
346-
// }
347-
//}
347+
/**
348+
* Wait out termination of a thread pool or fail doing so
349+
*/
350+
public void joinPool(TaskScheduler exec)
351+
{
352+
try
353+
{
354+
exec.Shutdown();
355+
assertTrue(exec.AwaitTermination(TimeSpan.FromMilliseconds(LONG_DELAY_MS)));
356+
}
357+
// catch (SecurityException ok) // LUCENENET - not needed
358+
// {
359+
// // Allowed in case test doesn't have privs
360+
// }
361+
catch (ThreadInterruptedException /*ie*/)
362+
{
363+
fail("Unexpected exception");
364+
}
365+
}
348366

349367

350368
/**
@@ -363,7 +381,141 @@ public void unexpectedException()
363381
fail("Unexpected exception");
364382
}
365383

384+
internal void ShortRunnable()
385+
{
386+
try
387+
{
388+
Thread.Sleep(SHORT_DELAY_MS);
389+
}
390+
catch (Exception e)
391+
{
392+
threadUnexpectedException(e);
393+
}
394+
}
395+
396+
internal void MediumRunnable()
397+
{
398+
try
399+
{
400+
Thread.Sleep(MEDIUM_DELAY_MS);
401+
}
402+
catch (Exception e)
403+
{
404+
threadUnexpectedException(e);
405+
}
406+
}
366407

367408
// LUCENENET TODO: Complete port
368409
}
410+
411+
/// <summary>
412+
/// LUCENENET specific - fake support for an API that feels like ThreadPoolExecutor.
413+
/// </summary>
414+
internal static class JSR166TestCaseExtensions
415+
{
416+
/// <summary>
417+
/// LUCENENET specific - state to keep track of tasks.
418+
/// <see cref="LimitedConcurrencyLevelTaskScheduler"/> removes tasks from the list when they complete,
419+
/// so this class is needed to keep track of them.
420+
/// </summary>
421+
private class TaskState
422+
{
423+
private readonly TaskFactory _factory;
424+
private readonly List<Task> _tasks = new();
425+
426+
public TaskState(TaskScheduler scheduler)
427+
{
428+
_factory = new TaskFactory(scheduler);
429+
}
430+
431+
public void NewTask(Action action)
432+
{
433+
var task = _factory.StartNew(action);
434+
_tasks.Add(task);
435+
}
436+
437+
public int ActiveCount => _tasks.Count(t => t.Status == TaskStatus.Running);
438+
439+
public int CompletedCount => _tasks.Count(t => t.IsCompleted);
440+
441+
public int TaskCount => _tasks.Count;
442+
443+
public bool AllCompleted => _tasks.All(t => t.IsCompleted);
444+
445+
public bool JoinAll(TimeSpan timeout) => Task.WhenAll(_tasks).Wait(timeout);
446+
}
447+
448+
private static readonly ConditionalWeakTable<TaskScheduler, TaskState> _taskFactories = new();
449+
450+
public static void Execute(this TaskScheduler scheduler, Action action)
451+
{
452+
if (!_taskFactories.TryGetValue(scheduler, out TaskState? state))
453+
{
454+
state = new TaskState(scheduler);
455+
_taskFactories.Add(scheduler, state);
456+
}
457+
458+
state.NewTask(action);
459+
}
460+
461+
public static bool AwaitTermination(this TaskScheduler scheduler, TimeSpan timeout)
462+
{
463+
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
464+
{
465+
return state.JoinAll(timeout);
466+
}
467+
468+
return true;
469+
}
470+
471+
public static int GetActiveCount(this TaskScheduler scheduler)
472+
{
473+
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
474+
{
475+
// Approximate the number of running threads, which shouldn't exceed the concurrency level
476+
return Math.Min(scheduler.MaximumConcurrencyLevel, state.ActiveCount);
477+
}
478+
479+
return 0;
480+
}
481+
482+
public static int GetCompletedTaskCount(this TaskScheduler scheduler)
483+
{
484+
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
485+
{
486+
return state.CompletedCount;
487+
}
488+
489+
return 0;
490+
}
491+
492+
public static int GetTaskCount(this TaskScheduler scheduler)
493+
{
494+
if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
495+
{
496+
return state.TaskCount;
497+
}
498+
499+
return 0;
500+
}
501+
502+
public static void Shutdown(this TaskScheduler scheduler)
503+
{
504+
if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl)
505+
{
506+
lcl.Shutdown();
507+
}
508+
}
509+
510+
public static bool IsTerminated(this TaskScheduler scheduler)
511+
{
512+
if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl
513+
&& _taskFactories.TryGetValue(scheduler, out TaskState? state))
514+
{
515+
return lcl.IsShutdown && state.AllCompleted;
516+
}
517+
518+
return false; // can't be shut down, so can't be terminated
519+
}
520+
}
369521
}

0 commit comments

Comments
 (0)
Please sign in to comment.