package org.warp.commonutils.locks; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class TransitLockTest { @Test public void testTransit() { var lock = new TransitLock(); lock.transit(); lock.transit(); lock.transit(); } @Test public void testPartialTransit() { var lock = new TransitLock(); lock.startTransit(); lock.endTransit(); lock.startTransit(); lock.endTransit(); lock.startTransit(); lock.endTransit(); } @Test public void testAllowTransit() { var lock = new TransitLock(); lock.disallowTransit(); lock.allowTransit(); lock.transit(); } @Test public void testMultiAllowTransit() { Assertions.assertThrows(IllegalStateException.class, () -> { var lock = new TransitLock(); lock.allowTransit(); lock.allowTransit(); lock.allowTransit(); lock.transit(); }); } @Test public void testMultiDisallowAllowTransit() { var lock = new TransitLock(); lock.disallowTransit(); lock.allowTransit(); lock.disallowTransit(); lock.disallowTransit(); lock.allowTransit(); lock.allowTransit(); lock.transit(); } @Test public void testDisallowTransit() { var lock = new TransitLock(); lock.disallowTransit(); for (int i = 0; i < 10; i++) { Assertions.assertThrows(TimeoutException.class, () -> { CompletableFuture.runAsync(lock::transit).get(5, TimeUnit.MILLISECONDS); }, "Disallowed transit didn't block a transit"); } } @Test public void testMultiDisallowTransit() { var lock = new TransitLock(); lock.disallowTransit(); lock.disallowTransit(); lock.disallowTransit(); for (int i = 0; i < 10; i++) { Assertions.assertThrows(TimeoutException.class, () -> { CompletableFuture.runAsync(lock::transit).get(5, TimeUnit.MILLISECONDS); }, "Disallowed transit didn't block a transit"); } } @Test public void testDeadlocks() { var lock = new TransitLock(); AtomicInteger alreadyRunningTransits = new AtomicInteger(); var pool = Executors.newFixedThreadPool(500); AtomicReference failure = new AtomicReference<>(); for (int i = 0; i < 100; i++) { int iF = i; pool.submit(() -> { try { for (int j = 0; j < 500; j++) { if (iF % 2 == 0) { lock.startTransit(); alreadyRunningTransits.getAndIncrement(); alreadyRunningTransits.decrementAndGet(); lock.endTransit(); } else { lock.disallowTransit(); Assertions.assertEquals(0, alreadyRunningTransits.get()); lock.allowTransit(); } } } catch (Exception e) { e.printStackTrace(); failure.set(e); } }); } pool.shutdown(); if (failure.get() != null) throw new AssertionError(failure.get()); Assertions.assertDoesNotThrow(() -> pool.awaitTermination(10, TimeUnit.SECONDS)); Assertions.assertTrue(pool.isTerminated()); } @Test public void testParallelTransit() { var lock = new TransitLock(); AtomicInteger alreadyRunningTransits = new AtomicInteger(); var pool = Executors.newFixedThreadPool(100); AtomicReference failure = new AtomicReference<>(); for (int i = 0; i < 100; i++) { pool.submit(() -> { try { lock.startTransit(); alreadyRunningTransits.getAndIncrement(); try { Thread.sleep(20); } catch (InterruptedException e) { Assertions.fail(e); } alreadyRunningTransits.decrementAndGet(); lock.endTransit(); } catch (AssertionError e) { e.printStackTrace(); failure.set(e); } }); } lock.disallowTransit(); Assertions.assertEquals(0, alreadyRunningTransits.get()); lock.allowTransit(); pool.shutdown(); if (failure.get() != null) throw failure.get(); Assertions.assertDoesNotThrow(() -> pool.awaitTermination(10, TimeUnit.SECONDS)); Assertions.assertTrue(pool.isTerminated()); } }