2323
2424import java .util .concurrent .CountDownLatch ;
2525import java .util .concurrent .TimeUnit ;
26+ import java .util .concurrent .atomic .AtomicReference ;
2627
2728/**
2829 * Unit cases for {@link UninterruptibleUtils}
@@ -162,4 +163,126 @@ public void testZeroSleepReturnsQuickly() {
162163 Assert .assertTrue ("Zero sleep should return quickly" , elapsedMillis < 50L );
163164 }
164165
166+ @ Test
167+ public void testNullThread () {
168+ Assert .assertThrows (NullPointerException .class ,
169+ () -> UninterruptibleUtils .joinUninterruptibly (null ));
170+ }
171+
172+ @ Test
173+ public void testReturnsWhenThreadFinishesBeforeTimeout () throws Exception {
174+ final long workMillis = 10L ;
175+ final Thread worker = new Thread (() -> {
176+ try {
177+ Thread .sleep (workMillis );
178+ } catch (InterruptedException ignored ) {}
179+ });
180+
181+ worker .start ();
182+
183+ long start = System .nanoTime ();
184+ UninterruptibleUtils .joinUninterruptibly (worker );
185+ long elapsedMillis = TimeUnit .NANOSECONDS .toMillis (System .nanoTime () - start );
186+
187+ Assert .assertFalse ("Worker should be finished" , worker .isAlive ());
188+ Assert .assertTrue ("Join should not take excessively long" ,
189+ elapsedMillis >= workMillis && elapsedMillis < 100L );
190+ }
191+
192+ @ Test
193+ public void testJoinShouldWaitUntilThreadFinishes () {
194+ final Thread worker = new Thread (() -> {
195+ try {
196+ Thread .sleep (20L );
197+ } catch (InterruptedException ignored ) {
198+ }
199+ });
200+
201+ worker .start ();
202+
203+ long start = System .nanoTime ();
204+ UninterruptibleUtils .joinUninterruptibly (worker );
205+ long elapsedMillis = TimeUnit .NANOSECONDS .toMillis (System .nanoTime () - start );
206+
207+ Assert .assertTrue ("Join should respect timeout" , elapsedMillis >= 20L );
208+ }
209+
210+ @ Test
211+ public void testJoinUninterruptiblyIgnoresInterruptsButRestoresFlag () throws Exception {
212+ final Thread worker = new Thread (() -> {
213+ try {
214+ Thread .sleep (200L );
215+ } catch (InterruptedException ignored ) {
216+ }
217+ });
218+
219+ worker .start ();
220+
221+ final AtomicReference <Throwable > t = new AtomicReference <>();
222+
223+ Thread joiningThread = new Thread (() -> {
224+ try {
225+ UninterruptibleUtils .joinUninterruptibly (worker );
226+ // After returning, interrupted flag should be set if we interrupted during join
227+ Assert .assertTrue ("Interrupt flag should be restored" , Thread .currentThread ().isInterrupted ());
228+ } catch (Throwable e ) {
229+ t .set (e );
230+ }
231+ });
232+
233+ joiningThread .start ();
234+
235+ // Let the joining thread enter join
236+ Thread .sleep (5L );
237+
238+ // Interrupt while it is joining
239+ joiningThread .interrupt ();
240+
241+ joiningThread .join ();
242+
243+ // fail if any exception occurred in the thread
244+ if (t .get () != null ) {
245+ Assert .fail ("Got exception: " + t .get ());
246+ }
247+ }
248+
249+ @ Test
250+ public void testJoinUninterruptiblyMultipleInterruptsStillCompleteAndRestoreFlag () throws Exception {
251+ final Thread worker = new Thread (() -> {
252+ try {
253+ Thread .sleep (300L );
254+ } catch (InterruptedException ignored ) {
255+ }
256+ });
257+
258+ worker .start ();
259+
260+ final AtomicReference <Throwable > t = new AtomicReference <>();
261+
262+ Thread joiningThread = new Thread (() -> {
263+ try {
264+ UninterruptibleUtils .joinUninterruptibly (worker );
265+ Assert .assertTrue ("Interrupt flag should be restored after multiple interrupts" ,
266+ Thread .currentThread ().isInterrupted ());
267+ } catch (Throwable e ) {
268+ t .set (e );
269+ }
270+ });
271+
272+ joiningThread .start ();
273+
274+ // Interrupt the joining thread multiple times while it is waiting
275+ for (int i = 0 ; i < 3 ; i ++) {
276+ Thread .sleep (5L );
277+ joiningThread .interrupt ();
278+ }
279+
280+ joiningThread .join ();
281+
282+ // fail if any exception occurred in the thread
283+ if (t .get () != null ) {
284+ Assert .fail ("Got exception: " + t .get ());
285+ }
286+ }
287+
165288}
0 commit comments