--- /dev/null
+package jalview.workers;
+
+import static org.testng.Assert.*;
+
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import jalview.api.AlignCalcManagerI2;
+import jalview.api.AlignCalcWorkerI;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.JvOptionPane;
+
+@Test(singleThreaded = true)
+public class AlignCaclManager2Test
+{
+ AlignFrame alignFrame;
+
+ AlignCalcManagerI2 calcManager;
+
+ @BeforeClass(alwaysRun = true)
+ public void setUpClass()
+ {
+ JvOptionPane.setInteractiveMode(false);
+ JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+ }
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp()
+ {
+ AlignmentI al = new Alignment(
+ new SequenceI[] { new Sequence("Seq1", "ABC") });
+ al.setDataset(null);
+ alignFrame = new AlignFrame(al, 3, 1);
+ calcManager = alignFrame.getViewport().getCalcManager();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown()
+ {
+ calcManager.shutdown();
+ }
+
+ // Running workers
+
+ @Test(groups = "Functional")
+ public void testStartRegisteredWorker() throws InterruptedException
+ {
+ var sentinel = new Object();
+ var job = CompletableFuture.completedFuture(sentinel);
+ var worker = new AlignCalcWorkerMock(job);
+ calcManager.registerWorker(worker);
+ Thread.sleep(10);
+ assertSame(worker.getLastResult(), sentinel);
+ }
+
+ @Test(groups = "Functional")
+ public void testIsWorking() throws InterruptedException
+ {
+ var job = new CompletableFuture<Object>();
+ var worker = new AlignCalcWorkerMock(job);
+ calcManager.registerWorker(worker);
+ assertTrue(calcManager.isWorking(worker));
+ assertTrue(calcManager.isWorking());
+ job.complete(null);
+ Thread.sleep(10);
+ assertFalse(calcManager.isWorking(worker));
+ assertFalse(calcManager.isWorking());
+ }
+
+ @Test(groups = "Functional")
+ public void testIsWorkingWithAnnotation() throws InterruptedException
+ {
+ var job = new CompletableFuture<Void>();
+ var worker1 = new AlignCalcWorkerMock(job);
+ var annot = worker1.annotation = newAlignmentAnnotation();
+ var otherAnnot = newAlignmentAnnotation();
+ calcManager.registerWorker(worker1);
+ assertTrue(calcManager.isWorkingWithAnnotation(annot));
+ assertFalse(calcManager.isWorkingWithAnnotation(otherAnnot));
+ job.complete(null);
+ Thread.sleep(10);
+ assertFalse(calcManager.isWorkingWithAnnotation(annot));
+ }
+
+ @Test(groups = "Functional")
+ public void testRestartCompletedWorkers() throws Throwable
+ {
+ var sentinel1 = new Object();
+ var sentinel2 = new Object();
+ var job = CompletableFuture.completedFuture(sentinel1);
+ var worker = new AlignCalcWorkerMock(job);
+ calcManager.registerWorker(worker);
+ Thread.sleep(10);
+ assertSame(worker.getLastResult(), sentinel1);
+ job.obtrudeValue(sentinel2);
+ calcManager.restartWorkers();
+ Thread.sleep(10);
+ assertSame(worker.getLastResult(), sentinel2);
+ }
+
+ @Test(groups = "Functional")
+ public void testRestartCancelsWorkers() throws Throwable
+ {
+ var job = new CompletableFuture<Object>();
+ var worker = new AlignCalcWorkerMock(job);
+ var sentinel = new Object();
+ calcManager.registerWorker(worker);
+ Thread.sleep(10);
+ calcManager.restartWorkers();
+ Thread.sleep(10);
+ assertTrue(worker.wasCancelled());
+ job.obtrudeValue(sentinel);
+ Thread.sleep(10);
+ assertSame(worker.getLastResult(), sentinel);
+ }
+
+ // Disabling workers
+
+ @Test(groups = "Functional")
+ public void testDisableWorker()
+ {
+ var worker = new AlignCalcWorkerMock(null);
+ calcManager.registerWorker(worker);
+ calcManager.disableWorker(worker);
+ assertTrue(calcManager.isDisabled(worker));
+ calcManager.enableWorker(worker);
+ assertFalse(calcManager.isDisabled(worker));
+ }
+
+ @Test(groups = "Functional")
+ public void testRestartDisabledWorker() throws InterruptedException
+ {
+ var worker = new AlignCalcWorkerMock(null);
+ calcManager.registerWorker(worker);
+ Thread.sleep(10);
+ assertEquals(worker.getCallCount(), 1);
+ calcManager.disableWorker(worker);
+ calcManager.restartWorkers();
+ Thread.sleep(10);
+ assertEquals(worker.getCallCount(), 1);
+ calcManager.enableWorker(worker);
+ calcManager.restartWorkers();
+ Thread.sleep(10);
+ assertEquals(worker.getCallCount(), 2);
+ }
+
+ // Canceling workers
+
+ @Test(groups = "Functional")
+ public void testCancelWorker() throws InterruptedException
+ {
+ var worker = new AlignCalcWorkerMock(new CompletableFuture<>());
+ calcManager.registerWorker(worker);
+ Thread.sleep(10);
+ calcManager.cancelWorker(worker);
+ Thread.sleep(10);
+ assertTrue(worker.wasCancelled());
+ }
+
+ // One-shot workers
+
+ @Test(groups = "Functional")
+ public void testStartOneShotWorker() throws InterruptedException
+ {
+ var job = CompletableFuture.completedFuture("result");
+ var worker = new AlignCalcWorkerMock(job);
+ calcManager.startWorker(worker);
+ Thread.sleep(10);
+ assertEquals(worker.getLastResult(), "result");
+ }
+
+ @Test(groups = "Functional")
+ public void testCancelOneShotWorker() throws InterruptedException
+ {
+ var worker = new AlignCalcWorkerMock(new CompletableFuture<>());
+ calcManager.startWorker(worker);
+ Thread.sleep(10);
+ calcManager.cancelWorker(worker);
+ Thread.sleep(10);
+ assertTrue(worker.wasCancelled());
+ }
+
+ @Test(groups = "Functional")
+ public void restartOneShotWorker() throws InterruptedException
+ {
+ var job = CompletableFuture.completedFuture("result1");
+ var worker = new AlignCalcWorkerMock(job);
+ calcManager.startWorker(worker);
+ Thread.sleep(10);
+ job.obtrudeValue("result2");
+ calcManager.restartWorkers();
+ Thread.sleep(10);
+
+ }
+
+
+ // Retrieving workers
+
+ @Test(groups = "Functional")
+ public void testGetWorkersOfClass() throws InterruptedException
+ {
+ var worker1 = new AlignCalcWorkerMock(null);
+ var worker2 = new AlignCalcWorkerMock(null);
+ var worker3 = new AlignCalcWorkerMock(null) {};
+ calcManager.registerWorker(worker1);
+ calcManager.registerWorker(worker2);
+ calcManager.registerWorker(worker3);
+ final var workers = calcManager
+ .getWorkersOfClass(AlignCalcWorkerMock.class);
+ assertTrue(workers.contains(worker1) && workers.contains(worker2));
+ assertFalse(workers.contains(worker3));
+ }
+
+ // Removing workers
+
+ @Test(groups = "Functional")
+ public void testRemoveWorker()
+ {
+ var worker = new AlignCalcWorkerMock(null);
+ calcManager.registerWorker(worker);
+ calcManager.removeWorker(worker);
+ assertFalse(calcManager.getWorkers().contains(worker));
+ }
+
+ @Test(groups = "Functional")
+ public void testRemoveWorkersOfClass()
+ {
+ var worker1 = new AlignCalcWorkerMock(null);
+ var worker2 = new AlignCalcWorkerMock(null);
+ var worker3 = new AlignCalcWorkerMock(null) {};
+ calcManager.registerWorker(worker1);
+ calcManager.registerWorker(worker2);
+ calcManager.registerWorker(worker3);
+ calcManager.removeWorkersOfClass(worker1.getClass());
+ assertFalse(calcManager.getWorkers().contains(worker1)
+ || calcManager.getWorkers().contains(worker2));
+ assertTrue(calcManager.getWorkers().contains(worker3));
+ }
+
+ @Test(groups = "Functional")
+ public void testRemoveWorkersForAnnotation()
+ {
+ var worker1 = new AlignCalcWorkerMock(null);
+ var worker2 = new AlignCalcWorkerMock(null);
+ var annot = worker1.annotation = newAlignmentAnnotation();
+ calcManager.registerWorker(worker1);
+ calcManager.registerWorker(worker2);
+ calcManager.removeWorkerForAnnotation(annot);
+ var workers = calcManager.getWorkers();
+ assertFalse(workers.contains(worker1));
+ assertTrue(workers.contains(worker2));
+ }
+
+ @Test(groups = "Functional")
+ public void testRemoveNonRemovableWorker()
+ {
+ var worker = new AlignCalcWorkerMock(null);
+ worker.deletable = false;
+ calcManager.registerWorker(worker);
+ calcManager.removeWorker(worker);
+ assertTrue(calcManager.getWorkers().contains(worker));
+ }
+
+ @Test(groups = "Functional")
+ public void testRemoveNonRemovableWorkersOfClass()
+ {
+ var worker1 = new AlignCalcWorkerMock(null);
+ var worker2 = new AlignCalcWorkerMock(null);
+ worker2.deletable = false;
+ calcManager.registerWorker(worker1);
+ calcManager.registerWorker(worker2);
+ calcManager.removeWorkersOfClass(worker1.getClass());
+ var workers = calcManager.getWorkers();
+ assertFalse(workers.contains(worker1));
+ assertTrue(workers.contains(worker2));
+ }
+
+ private int annotationCount = 0;
+
+ private AlignmentAnnotation newAlignmentAnnotation()
+ {
+ return new AlignmentAnnotation("Ann" + annotationCount++, "description",
+ new Annotation[] {});
+ }
+}
+
+class AlignCalcWorkerMock implements AlignCalcWorkerI
+{
+ AlignmentAnnotation annotation = null;
+ Future<?> job;
+ ArrayList<Object> values = new ArrayList<>();
+ int callCount = 0;
+ boolean deletable = true;
+
+ AlignCalcWorkerMock(Future<?> job)
+ {
+ this.job = job;
+ }
+
+ public Object getLastResult()
+ {
+ return values.isEmpty() ? null : values.get(values.size() - 1);
+ }
+
+ public Throwable getException()
+ {
+ var result = getLastResult();
+ return (result instanceof Throwable) ? (Throwable) result : null;
+ }
+
+ public int getCallCount() {
+ return callCount;
+ }
+
+ public boolean wasCancelled()
+ {
+ return getException() instanceof InterruptedException;
+ }
+
+ @Override
+ public boolean involves(AlignmentAnnotation annot)
+ {
+ if (annotation == null)
+ return false;
+ else
+ return annot == annotation;
+ }
+
+ @Override
+ public void updateAnnotation()
+ {
+ }
+
+ @Override
+ public void removeAnnotation()
+ {
+ }
+
+ @Override
+ public void run() throws Throwable
+ {
+ callCount++;
+ if (job != null)
+ {
+ try
+ {
+ values.add(job.get());
+ }
+ catch (InterruptedException e) { values.add(e); }
+ catch (CancellationException e) {
+ values.add(new InterruptedException());
+ } catch (ExecutionException e)
+ {
+ values.add(e.getCause());
+ }
+ }
+ }
+
+ @Override
+ public boolean isDeletable()
+ {
+ return deletable;
+ }
+}