Merge branch 'feature/JAL-3690_callback-based-web-services' into alpha/JAL-3066_Jalvi...
[jalview.git] / test / jalview / workers / AlignCaclManager2Test.java
diff --git a/test/jalview/workers/AlignCaclManager2Test.java b/test/jalview/workers/AlignCaclManager2Test.java
new file mode 100644 (file)
index 0000000..343ad14
--- /dev/null
@@ -0,0 +1,377 @@
+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;
+  }
+}