Merge branch 'mmw/JAL-4199-task-execution-update' into development/Release_2_12_Branch
[jalview.git] / test / jalview / ws2 / actions / alignment / AlignmentActionTest.java
1 package jalview.ws2.actions.alignment;
2
3 import java.io.IOException;
4 import java.net.URL;
5 import java.util.List;
6 import java.util.concurrent.CountDownLatch;
7 import java.util.concurrent.TimeUnit;
8
9 import javax.help.UnsupportedOperationException;
10
11 import org.hamcrest.BaseMatcher;
12 import org.hamcrest.Description;
13 import org.hamcrest.Matcher;
14 import org.hamcrest.TypeSafeMatcher;
15 import org.mockito.ArgumentCaptor;
16 import org.testng.annotations.BeforeMethod;
17 import org.testng.annotations.DataProvider;
18 import org.testng.annotations.Test;
19
20 import jalview.datamodel.Alignment;
21 import jalview.datamodel.AlignmentI;
22 import jalview.datamodel.Sequence;
23 import jalview.datamodel.SequenceI;
24 import jalview.gui.AlignViewport;
25 import jalview.viewmodel.AlignmentViewport;
26 import jalview.ws.params.ParamDatastoreI;
27 import jalview.ws2.actions.PollingTaskExecutor;
28 import jalview.ws2.actions.api.JobI;
29 import jalview.ws2.actions.api.TaskEventListener;
30 import jalview.ws2.api.Credentials;
31 import jalview.ws2.api.JobStatus;
32 import jalview.ws2.api.WebService;
33 import jalview.ws2.api.WebServiceJobHandle;
34 import jalview.ws2.client.api.AlignmentWebServiceClientI;
35
36 import org.mockito.hamcrest.MockitoHamcrest;
37 import org.mockito.internal.hamcrest.HamcrestArgumentMatcher;
38
39 import static org.mockito.Mockito.*;
40 import static org.hamcrest.MatcherAssert.assertThat;
41 import static org.hamcrest.Matchers.*;
42
43 public class AlignmentActionTest
44 {
45   protected AlignmentWebServiceClientI mockClient;
46
47   protected AlignmentAction.Builder actionBuilder;
48
49   protected WebServiceJobHandle jobRef;
50
51   @BeforeMethod
52   public void setupMockClient() throws IOException
53   {
54     jobRef = new WebServiceJobHandle(
55         "mock", "mock", "http://example.org", "00000001");
56     mockClient = mock(AlignmentWebServiceClientI.class);
57     when(mockClient.getUrl()).thenReturn("http://example.org");
58     when(mockClient.getClientName()).thenReturn("mock");
59     when(mockClient.submit(anyList(), anyList(), any())).thenReturn(jobRef);
60     when(mockClient.getLog(jobRef)).thenReturn("");
61     when(mockClient.getErrorLog(jobRef)).thenReturn("");
62     doThrow(new UnsupportedOperationException()).when(mockClient).cancel(any());
63   }
64
65   @BeforeMethod(dependsOnMethods = { "setupMockClient" })
66   public void setupActionBuilder() throws IOException
67   {
68     actionBuilder = AlignmentAction.newBuilder(mockClient);
69     actionBuilder.name("mock");
70     actionBuilder.webService(
71         WebService.<AlignmentAction> newBuilder()
72             .url(new URL("http://example.org"))
73             .clientName("mock")
74             .category("Alignment")
75             .name("mock")
76             .paramDatastore(mock(ParamDatastoreI.class))
77             .actionClass(AlignmentAction.class)
78             .build());
79   }
80
81   @DataProvider
82   public Object[][] multipleSequencesUnalignedAndAligned()
83   {
84     return new Object[][] {
85         {
86             new Alignment(new SequenceI[]
87             {
88                 new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"),
89                 new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"),
90                 new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------")
91             }),
92             new Alignment(new SequenceI[]
93             {
94                 new Sequence("Sequence0", "ASTV-LITOPDCMMQEGGST----"),
95                 new Sequence("Sequence1", "ASC-GLITO---MMQEGGST----"),
96                 new Sequence("Sequence2", "ASTV-L--OPDTMMQE--L-----")
97             })
98         }
99     };
100   }
101
102   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
103   public void submitSequences_verifySequenceNamesUniquified(
104       Alignment unaligned, Alignment aligned)
105       throws IOException
106   {
107     var viewport = new AlignViewport(unaligned);
108     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
109     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
110     actionBuilder.submitGaps(false);
111     performAction(viewport, actionBuilder.build());
112     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
113     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
114     assertThat(argument.getValue(),
115         contains(hasProperty("name", is("Sequence0")),
116             hasProperty("name", is("Sequence1")),
117             hasProperty("name", is("Sequence2"))));
118   }
119
120   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
121   public void submitSequences_submitGapsOff_verifySequencesSubmittedWithoutGaps(Alignment unaligned, Alignment aligned)
122       throws IOException
123   {
124     var viewport = new AlignViewport(unaligned);
125     actionBuilder.submitGaps(false);
126     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
127     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
128     performAction(viewport, actionBuilder.build());
129     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
130     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
131     assertThat(argument.getValue(),
132         contains(
133             matchesSequence("ASTVLITOPDCMMQEGGST"),
134             matchesSequence("ASCGLITOMMQEGGST"),
135             matchesSequence("ASTVLOPDTMMQEL")));
136   }
137
138   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
139   public void submitSequences_submitGapsOn_verifySequencesSubmittedWithGaps(
140       Alignment unaligned, Alignment aligned)
141       throws IOException
142   {
143     var viewport = new AlignViewport(unaligned);
144     actionBuilder.submitGaps(true);
145     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
146     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
147     performAction(viewport, actionBuilder.build());
148     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
149     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
150     assertThat(argument.getValue(),
151         contains(
152             matchesSequence("----ASTVLITOPDCMMQEGGST-"),
153             matchesSequence("-ASCGLITO------MMQEGGST-"),
154             matchesSequence("AS--TVL--OPDTMMQEL------")));
155   }
156
157   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
158   public void retrieveResult_verifySequencesAligned(
159       Alignment unaligned, Alignment aligned)
160       throws IOException
161   {
162     var viewport = new AlignViewport(unaligned);
163     actionBuilder.submitGaps(false);
164     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
165     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
166     var mockListener = performAction(viewport, actionBuilder.build());
167     var argument = ArgumentCaptor.forClass(AlignmentResult.class);
168     verify(mockListener).taskCompleted(any(), argument.capture());
169     var alignmentResult = argument.getValue().getAlignment();
170     assertThat(alignmentResult, hasProperty("sequences", contains(
171         matchesSequence("ASTV-LITOPDCMMQEGGST----"),
172         matchesSequence("ASC-GLITO---MMQEGGST----"),
173         matchesSequence("ASTV-L--OPDTMMQE--L-----"))));
174   }
175
176   protected static Matcher<SequenceI> matchesSequence(String sequence)
177   {
178     return new TypeSafeMatcher<SequenceI>()
179     {
180       @Override
181       public boolean matchesSafely(SequenceI obj)
182       {
183         if (!(obj instanceof SequenceI))
184           return false;
185         var seq = (SequenceI) obj;
186         return seq.getSequenceAsString().equals(sequence);
187       }
188
189       @Override
190       public void describeTo(Description description)
191       {
192         description.appendText("a sequence ").appendValue(sequence);
193       }
194
195       @Override
196       public void describeMismatchSafely(SequenceI item, Description description)
197       {
198         description.appendText("was ").appendValue(item.getSequenceAsString());
199       }
200     };
201   }
202
203   protected TaskEventListener<AlignmentResult> performAction(
204       AlignmentViewport viewport, AlignmentAction action)
205       throws IOException
206   {
207     TaskEventListener<AlignmentResult> listener = mock(TaskEventListener.class);
208     var latch = new CountDownLatch(1);
209     doAnswer(invocation -> {
210       latch.countDown();
211       return null;
212     })
213         .when(listener).taskCompleted(any(), any());
214     var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
215     var task = action.createTask(viewport, List.of(), Credentials.empty());
216     task.addTaskEventListener(listener);
217     var cancellable = executor.submit(task);
218     try
219     {
220       latch.await(100, TimeUnit.MILLISECONDS);
221     } catch (InterruptedException e)
222     {
223       cancellable.cancel(true);
224     }
225     return listener;
226   }
227 }
228
229 class AlignmentActionListenerNotifiedTest extends AlignmentActionTest
230 {
231   private AlignViewport viewport;
232
233   @BeforeMethod
234   public void setupViewport()
235   {
236     viewport = new AlignViewport(new Alignment(new SequenceI[] {
237         new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"),
238         new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"),
239         new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------")
240     }));
241   }
242
243   @DataProvider
244   public JobStatus[] jobStatuses()
245   {
246     // CREATED, INVALID and READY should not be returned by the server
247     return new JobStatus[] {
248         JobStatus.SUBMITTED,
249         JobStatus.QUEUED,
250         JobStatus.RUNNING,
251         JobStatus.COMPLETED,
252         JobStatus.FAILED,
253         JobStatus.CANCELLED,
254         JobStatus.SERVER_ERROR,
255         JobStatus.UNKNOWN
256     };
257   }
258
259   @Test
260   public void allJobsStarted_taskStartedCalled()
261       throws IOException
262   {
263     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
264     var mockListener = performAction(viewport, actionBuilder.build());
265     verify(mockListener).taskStarted(any(), anyList());
266   }
267
268   @Test
269   public void allJobsStarted_taskStatusChangedCalledWithReadyThenSubmitted()
270       throws IOException
271   {
272     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
273     var mockListener = performAction(viewport, actionBuilder.build());
274     var inOrder = inOrder(mockListener);
275     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.READY));
276     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.SUBMITTED));
277   }
278
279   @Test(dataProvider = "jobStatuses")
280   public void jobStatusChanged_taskStatusChangedCalledWithJobStatus(JobStatus status)
281       throws IOException
282   {
283     when(mockClient.getStatus(jobRef))
284         .thenReturn(status)
285         .thenReturn(JobStatus.COMPLETED);
286     var mockListener = performAction(viewport, actionBuilder.build());
287     verify(mockListener).taskStatusChanged(any(), eq(status));
288   }
289
290   @Test(dataProvider = "jobStatuses")
291   public void jobStatusChanged_subJobStatusChangedCalledWithJobStatus(JobStatus status)
292       throws IOException
293   {
294     when(mockClient.getStatus(jobRef))
295         .thenReturn(status)
296         .thenReturn(JobStatus.COMPLETED);
297     var mockListener = performAction(viewport, actionBuilder.build());
298     verify(mockListener).subJobStatusChanged(any(), any(), eq(status));
299   }
300 }