JAL-3690 Introduce AlignCalcManager2 tests. feature/JAL-3690_callback-based-web-services
authorMateusz <mmzwarowny@dundee.ac.uk>
Wed, 27 Jan 2021 14:36:07 +0000 (15:36 +0100)
committerMateusz <mmzwarowny@dundee.ac.uk>
Wed, 27 Jan 2021 14:36:07 +0000 (15:36 +0100)
src/jalview/api/AlignCalcManagerI2.java
src/jalview/workers/AlignCalcManager2.java
test/jalview/workers/AlignCaclManager2Test.java [new file with mode: 0644]

index 22b4832..0bd6c3c 100644 (file)
@@ -45,6 +45,13 @@ public interface AlignCalcManagerI2
   void removeWorkerForAnnotation(AlignmentAnnotation annot);
   
   /**
+   * Alias of removeWorkerForAnnotation
+   */
+  default void removeWorkersForAnnotation(AlignmentAnnotation annot) {
+    removeWorkerForAnnotation(annot);
+  }
+  
+  /**
    * Removes all workers of a given class. The classes are compared using
    * {@link Class#equals(Object)}. 
    */
index 8340900..8d4796d 100644 (file)
@@ -59,7 +59,7 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
       {
         return;
       }
-      if (!isRestartable())
+      if (!isRegistered())
       {
         setEnabled(false);
       }
@@ -70,7 +70,7 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
       submit();
     }
 
-    protected boolean isRestartable()
+    protected boolean isRegistered()
     {
       return registered.containsKey(getWorker());
     }
@@ -125,7 +125,7 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
                   getWorker().getClass().getName()), th);
         } finally
         {
-          if (!isRestartable())
+          if (!isRegistered())
           {
             // delete worker reference so garbage collector can remove it
             worker = null;
@@ -217,7 +217,7 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
           if (completed)
           {
             final var worker = getWorker();
-            if (!isRestartable())
+            if (!isRegistered())
               PollableWorkerManager.super.worker = null;
             Cache.log.debug(format("Finalizing completed worker %s",
                     worker.getClass().getName()));
@@ -243,7 +243,7 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
       task.cancel(false);
       executor.submit(() -> {
         final var worker = getWorker();
-        if (!isRestartable())
+        if (!isRegistered())
           PollableWorkerManager.super.worker = null;
         if (worker != null)
         {
@@ -314,7 +314,10 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
   @Override
   public void removeWorker(AlignCalcWorkerI worker)
   {
-    registered.remove(worker);
+    if (worker.isDeletable())
+    {
+      registered.remove(worker);
+    }
   }
 
   @Override
@@ -324,7 +327,7 @@ public class AlignCalcManager2 implements AlignCalcManagerI2
     {
       for (var worker : getWorkers())
       {
-        if (worker.involves(annot) && worker.isDeletable())
+        if (worker.involves(annot))
         {
           removeWorker(worker);
         }
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;
+  }
+}