JAL-1601 Expand secondary structure prediction tests
[jalview.git] / test / jalview / ws2 / actions / alignment / AlignmentActionTest.java
1 package jalview.ws2.actions.alignment;
2
3 import static jalview.testutils.Matchers.matchesSequenceString;
4 import static org.hamcrest.MatcherAssert.assertThat;
5 import static org.hamcrest.Matchers.contains;
6 import static org.hamcrest.Matchers.hasProperty;
7 import static org.hamcrest.Matchers.is;
8 import static org.mockito.ArgumentMatchers.any;
9 import static org.mockito.ArgumentMatchers.anyList;
10 import static org.mockito.ArgumentMatchers.eq;
11 import static org.mockito.Mockito.doAnswer;
12 import static org.mockito.Mockito.doThrow;
13 import static org.mockito.Mockito.inOrder;
14 import static org.mockito.Mockito.mock;
15 import static org.mockito.Mockito.verify;
16 import static org.mockito.Mockito.when;
17
18 import java.io.IOException;
19 import java.net.URL;
20 import java.util.List;
21 import java.util.concurrent.CountDownLatch;
22 import java.util.concurrent.TimeUnit;
23
24 import org.mockito.ArgumentCaptor;
25 import org.testng.annotations.BeforeMethod;
26 import org.testng.annotations.DataProvider;
27 import org.testng.annotations.Test;
28
29 import jalview.datamodel.Alignment;
30 import jalview.datamodel.Sequence;
31 import jalview.datamodel.SequenceI;
32 import jalview.gui.AlignViewport;
33 import jalview.viewmodel.AlignmentViewport;
34 import jalview.ws.params.ParamDatastoreI;
35 import jalview.ws2.actions.PollingTaskExecutor;
36 import jalview.ws2.actions.api.TaskEventListener;
37 import jalview.ws2.api.Credentials;
38 import jalview.ws2.api.JobStatus;
39 import jalview.ws2.api.WebService;
40 import jalview.ws2.api.WebServiceJobHandle;
41 import jalview.ws2.client.api.AlignmentWebServiceClientI;
42
43 public class AlignmentActionTest
44 {
45   protected AlignmentWebServiceClientI mockClient;
46
47   protected AlignmentAction.Builder actionBuilder;
48
49   protected WebServiceJobHandle jobRef;
50
51   @BeforeMethod(alwaysRun = true)
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(alwaysRun = true, 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(
103     groups = { "Functional" },
104     dataProvider = "multipleSequencesUnalignedAndAligned")
105   public void submitSequences_verifySequenceNamesUniquified(
106       Alignment unaligned, Alignment aligned)
107       throws IOException
108   {
109     var viewport = new AlignViewport(unaligned);
110     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
111     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
112     actionBuilder.submitGaps(false);
113     performAction(viewport, actionBuilder.build());
114     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
115     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
116     assertThat(argument.getValue(),
117         contains(hasProperty("name", is("Sequence0")),
118             hasProperty("name", is("Sequence1")),
119             hasProperty("name", is("Sequence2"))));
120   }
121
122   @Test(
123     groups = { "Functional" },
124     dataProvider = "multipleSequencesUnalignedAndAligned")
125   public void submitSequences_submitGapsOff_verifySequencesSubmittedWithoutGaps(Alignment unaligned, Alignment aligned)
126       throws IOException
127   {
128     var viewport = new AlignViewport(unaligned);
129     actionBuilder.submitGaps(false);
130     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
131     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
132     performAction(viewport, actionBuilder.build());
133     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
134     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
135     assertThat(argument.getValue(),
136         contains(
137             matchesSequenceString("ASTVLITOPDCMMQEGGST"),
138             matchesSequenceString("ASCGLITOMMQEGGST"),
139             matchesSequenceString("ASTVLOPDTMMQEL")));
140   }
141
142   @Test(
143     groups = { "Functional" },
144     dataProvider = "multipleSequencesUnalignedAndAligned")
145   public void submitSequences_submitGapsOn_verifySequencesSubmittedWithGaps(
146       Alignment unaligned, Alignment aligned)
147       throws IOException
148   {
149     var viewport = new AlignViewport(unaligned);
150     actionBuilder.submitGaps(true);
151     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
152     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
153     performAction(viewport, actionBuilder.build());
154     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
155     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
156     assertThat(argument.getValue(),
157         contains(
158             matchesSequenceString("----ASTVLITOPDCMMQEGGST-"),
159             matchesSequenceString("-ASCGLITO------MMQEGGST-"),
160             matchesSequenceString("AS--TVL--OPDTMMQEL------")));
161   }
162
163   @Test(
164     groups = { "Functional" },
165     dataProvider = "multipleSequencesUnalignedAndAligned")
166   public void retrieveResult_verifySequencesAligned(
167       Alignment unaligned, Alignment aligned)
168       throws IOException
169   {
170     var viewport = new AlignViewport(unaligned);
171     actionBuilder.submitGaps(false);
172     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
173     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
174     var mockListener = performAction(viewport, actionBuilder.build());
175     var argument = ArgumentCaptor.forClass(AlignmentResult.class);
176     verify(mockListener).taskCompleted(any(), argument.capture());
177     var alignmentResult = argument.getValue().getAlignment();
178     assertThat(alignmentResult, hasProperty("sequences", contains(
179         matchesSequenceString("ASTV-LITOPDCMMQEGGST----"),
180         matchesSequenceString("ASC-GLITO---MMQEGGST----"),
181         matchesSequenceString("ASTV-L--OPDTMMQE--L-----"))));
182   }
183
184   protected TaskEventListener<AlignmentResult> performAction(
185       AlignmentViewport viewport, AlignmentAction action)
186       throws IOException
187   {
188     TaskEventListener<AlignmentResult> listener = mock(TaskEventListener.class);
189     var latch = new CountDownLatch(1);
190     doAnswer(invocation -> {
191       latch.countDown();
192       return null;
193     })
194         .when(listener).taskCompleted(any(), any());
195     var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
196     var task = action.createTask(viewport, List.of(), Credentials.empty());
197     task.addTaskEventListener(listener);
198     var cancellable = executor.submit(task);
199     try
200     {
201       latch.await(100, TimeUnit.MILLISECONDS);
202     } catch (InterruptedException e)
203     {
204       cancellable.cancel(true);
205     }
206     return listener;
207   }
208 }
209
210 class AlignmentActionListenerNotifiedTest extends AlignmentActionTest
211 {
212   private AlignViewport viewport;
213
214   @BeforeMethod(alwaysRun = true)
215   public void setupViewport()
216   {
217     viewport = new AlignViewport(new Alignment(new SequenceI[] {
218         new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"),
219         new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"),
220         new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------")
221     }));
222   }
223
224   @DataProvider
225   public JobStatus[] jobStatuses()
226   {
227     // CREATED, INVALID and READY should not be returned by the server
228     return new JobStatus[] {
229         JobStatus.SUBMITTED,
230         JobStatus.QUEUED,
231         JobStatus.RUNNING,
232         JobStatus.COMPLETED,
233         JobStatus.FAILED,
234         JobStatus.CANCELLED,
235         JobStatus.SERVER_ERROR,
236         JobStatus.UNKNOWN
237     };
238   }
239
240   @Test(groups = { "Functional" })
241   public void allJobsStarted_taskStartedCalled()
242       throws IOException
243   {
244     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
245     var mockListener = performAction(viewport, actionBuilder.build());
246     verify(mockListener).taskStarted(any(), anyList());
247   }
248
249   @Test(groups = { "Functional" })
250   public void allJobsStarted_taskStatusChangedCalledWithReadyThenSubmitted()
251       throws IOException
252   {
253     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
254     var mockListener = performAction(viewport, actionBuilder.build());
255     var inOrder = inOrder(mockListener);
256     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.READY));
257     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.SUBMITTED));
258   }
259
260   @Test(groups = { "Functional" }, dataProvider = "jobStatuses")
261   public void jobStatusChanged_taskStatusChangedCalledWithJobStatus(JobStatus status)
262       throws IOException
263   {
264     when(mockClient.getStatus(jobRef))
265         .thenReturn(status)
266         .thenReturn(JobStatus.COMPLETED);
267     var mockListener = performAction(viewport, actionBuilder.build());
268     verify(mockListener).taskStatusChanged(any(), eq(status));
269   }
270
271   @Test(groups = { "Functional" }, dataProvider = "jobStatuses")
272   public void jobStatusChanged_subJobStatusChangedCalledWithJobStatus(JobStatus status)
273       throws IOException
274   {
275     when(mockClient.getStatus(jobRef))
276         .thenReturn(status)
277         .thenReturn(JobStatus.COMPLETED);
278     var mockListener = performAction(viewport, actionBuilder.build());
279     verify(mockListener).subJobStatusChanged(any(), any(), eq(status));
280   }
281 }