Merge branch 'develop' into trialMerge
[jalview.git] / test / jalview / io / CrossRef2xmlTests.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import jalview.analysis.CrossRef;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.AlignedCodonFrame;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.AlignmentTest;
28 import jalview.datamodel.SequenceI;
29 import jalview.gui.AlignFrame;
30 import jalview.gui.CrossRefAction;
31 import jalview.gui.Desktop;
32 import jalview.gui.JvOptionPane;
33 import jalview.gui.SequenceFetcher;
34 import jalview.project.Jalview2XML;
35 import jalview.util.DBRefUtils;
36
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44
45 import org.testng.Assert;
46 import org.testng.annotations.BeforeClass;
47 import org.testng.annotations.DataProvider;
48 import org.testng.annotations.Test;
49
50 import junit.extensions.PA;
51
52 @Test(singleThreaded = true)
53 public class CrossRef2xmlTests extends Jalview2xmlBase
54 {
55
56   @Override
57   @BeforeClass(alwaysRun = true)
58   public void setUpJvOptionPane()
59   {
60     JvOptionPane.setInteractiveMode(false);
61     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
62   }
63
64   @DataProvider(name = "initialAccessions")
65   static Object[][] getAccessions()
66   {
67     return new String[][] { { "UNIPROT", "P00338" },
68         { "UNIPROT", "Q8Z9G6" },
69         { "ENSEMBLGENOMES", "CAD01290" } };
70   }
71
72   /**
73    * test store and recovery of all reachable cross refs from all reachable
74    * crossrefs for one or more fetched db refs. Currently, this test has a known
75    * failure case.
76    * 
77    * @throws Exception
78    */
79   @Test(
80     groups =
81     { "Operational" },
82     dataProvider = "initialAccessions",
83     enabled = true)
84   public void testRetrieveAndShowCrossref(String forSource,
85           String forAccession) throws Exception
86   {
87
88     List<String> failedDBRetr = new ArrayList<>();
89     List<String> failedXrefMenuItems = new ArrayList<>();
90     List<String> failedProjectRecoveries = new ArrayList<>();
91     // only search for ensembl or Uniprot crossrefs
92     List<String> limit = Arrays
93             .asList(new String[]
94             { DBRefUtils.getCanonicalName("ENSEMBL"),
95                 DBRefUtils.getCanonicalName("Uniprot") });
96     // for every set of db queries
97     // retrieve db query
98     // verify presence of expected xrefs
99     // show xrefs - verify expected type of frame is shown for each xref
100     // show xrefs again
101     // - verify original -> xref -> xref(original) recovers frame containing at
102     // least the first retrieved sequence
103     // store
104     // 1. whole project
105     // 2. individual frames
106     // 3. load each one back and verify
107     // . aligned sequences (.toString() )
108     // . xrefs (.toString() )
109     // . codonframes
110     //
111     //
112     Map<String, String> dbtoviewBit = new HashMap<>();
113     List<String> keyseq = new ArrayList<>();
114     Map<String, File> savedProjects = new HashMap<>();
115
116     // for (String[] did : new String[][] { { "UNIPROT", "P00338" } })
117     // {
118     // pass counters - 0 - first pass, 1 means retrieve project rather than
119     // perform action
120     int pass1 = 0, pass2 = 0, pass3 = 0;
121     // each do loop performs two iterations in the first outer loop pass, but
122     // only performs one iteration on the second outer loop
123     // ie. pass 1 = 0 {pass 2= 0 { pass 3 = 0,1 }, pass 2=1 { pass 3 = 0 }}, 1
124     // { pass 2 = 0 { pass 3 = 0 } }
125     do
126     {
127       String first = forSource + " " + forAccession;// did[0] + " " + did[1];
128       AlignFrame af = null;
129       boolean dna;
130       AlignmentI retral;
131       AlignmentI dataset;
132       SequenceI[] seqs;
133       List<String> ptypes = null;
134       if (pass1 == 0)
135       {
136         // retrieve dbref
137
138         SequenceFetcher sf = new SequenceFetcher(Desktop.instance,
139                 forSource, forAccession);
140         sf.run();
141         AlignFrame[] afs = Desktop.getAlignFrames();
142         if (afs.length == 0)
143         {
144           failedDBRetr.add("Didn't retrieve " + first);
145           break;
146         }
147         keyseq.add(first);
148         af = afs[0];
149
150         // verify references for retrieved data
151         AlignmentTest.assertAlignmentDatasetRefs(
152                 af.getViewport().getAlignment(), "Pass (" + pass1 + ","
153                         + pass2 + "," + pass3 + "): Fetch " + first + ":");
154         assertDatasetIsNormalisedKnownDefect(
155                 af.getViewport().getAlignment(), "Pass (" + pass1 + ","
156                         + pass2 + "," + pass3 + "): Fetch " + first + ":");
157         dna = af.getViewport().getAlignment().isNucleotide();
158         retral = af.getViewport().getAlignment();
159         dataset = retral.getDataset();
160         seqs = retral.getSequencesArray();
161
162       }
163       else
164       {
165         Desktop.instance.closeAll_actionPerformed(null);
166         // recover stored project
167         af = new FileLoader(false).LoadFileWaitTillLoaded(
168                 savedProjects.get(first).toString(), DataSourceType.FILE);
169         System.out.println("Recovered view for '" + first + "' from '"
170                 + savedProjects.get(first).toString() + "'");
171         dna = af.getViewport().getAlignment().isNucleotide();
172         retral = af.getViewport().getAlignment();
173         dataset = retral.getDataset();
174         seqs = retral.getSequencesArray();
175
176         // verify references for recovered data
177         AlignmentTest.assertAlignmentDatasetRefs(
178                 af.getViewport().getAlignment(),
179                 "Pass (" + pass1 + "," + pass2 + "," + pass3 + "): Recover "
180                         + first + ":");
181         assertDatasetIsNormalisedKnownDefect(
182                 af.getViewport().getAlignment(),
183                 "Pass (" + pass1 + "," + pass2 + "," + pass3 + "): Recover "
184                         + first + ":");
185
186       }
187
188       // store project on first pass, compare next pass
189       stringify(dbtoviewBit, savedProjects, first, af.alignPanel);
190
191       ptypes = (seqs == null || seqs.length == 0) ? null
192               : new CrossRef(seqs, dataset)
193                       .findXrefSourcesForSequences(dna);
194       filterDbRefs(ptypes, limit);
195
196       // start of pass2: retrieve each cross-ref for fetched or restored
197       // project.
198       do // first cross ref and recover crossref loop
199       {
200
201         for (String db : ptypes)
202         {
203           // counter for splitframe views retrieved via crossref
204           int firstcr_ap = 0;
205           // build next key so we an retrieve all views
206           String nextxref = first + " -> " + db + "{" + firstcr_ap + "}";
207           // perform crossref action, or retrieve stored project
208           List<AlignmentViewPanel> cra_views = new ArrayList<>();
209           CrossRefAction cra = null;
210
211           if (pass2 == 0)
212           { // retrieve and show cross-refs in this thread
213             cra = CrossRefAction.getHandlerFor(seqs, dna, db, af);
214             cra.run();
215             cra_views = (List<AlignmentViewPanel>) PA.getValue(cra,
216                     "xrefViews");
217             if (cra_views.size() == 0)
218             {
219               failedXrefMenuItems.add(
220                       "No crossrefs retrieved for " + first + " -> " + db);
221               continue;
222             }
223             assertNucleotide(cra_views.get(0),
224                     "Nucleotide panel included proteins for " + first
225                             + " -> " + db);
226             assertProtein(cra_views.get(1),
227                     "Protein panel included nucleotides for " + first
228                             + " -> " + db);
229           }
230           else
231           {
232             Desktop.instance.closeAll_actionPerformed(null);
233             pass3 = 0;
234             // recover stored project
235             File storedProject = savedProjects.get(nextxref);
236             if (storedProject == null)
237             {
238               failedProjectRecoveries
239                       .add("Failed to store a view for '" + nextxref + "'");
240               continue;
241             }
242
243             // recover stored project
244             AlignFrame af2 = new FileLoader(false).LoadFileWaitTillLoaded(
245                     savedProjects.get(nextxref).toString(),
246                     DataSourceType.FILE);
247             System.out
248                     .println("Recovered view for '" + nextxref + "' from '"
249                             + savedProjects.get(nextxref).toString() + "'");
250             // gymnastics to recover the alignPanel/Complementary alignPanel
251             if (af2.getViewport().isNucleotide())
252             {
253               // top view, then bottom
254               cra_views.add(af2.getViewport().getAlignPanel());
255               cra_views.add(((jalview.gui.AlignViewport) af2.getViewport()
256                       .getCodingComplement()).getAlignPanel());
257
258             }
259             else
260             {
261               // bottom view, then top
262               cra_views.add(((jalview.gui.AlignViewport) af2.getViewport()
263                       .getCodingComplement()).getAlignPanel());
264               cra_views.add(af2.getViewport().getAlignPanel());
265
266             }
267           }
268           HashMap<String, List<String>> xrptypes = new HashMap<>();
269           // first save/verify views.
270           for (AlignmentViewPanel avp : cra_views)
271           {
272             nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}";
273             // verify references for this panel
274             AlignmentTest.assertAlignmentDatasetRefs(avp.getAlignment(),
275                     "Pass (" + pass1 + "," + pass2 + "," + pass3
276                             + "): before start of pass3: " + nextxref
277                             + ":");
278             assertDatasetIsNormalisedKnownDefect(avp.getAlignment(),
279                     "Pass (" + pass1 + "," + pass2 + "," + pass3
280                             + "): before start of pass3: " + nextxref
281                             + ":");
282
283             SequenceI[] xrseqs = avp.getAlignment().getSequencesArray();
284
285             List<String> _xrptypes = (seqs == null || seqs.length == 0)
286                     ? null
287                     : new CrossRef(xrseqs, dataset)
288                             .findXrefSourcesForSequences(
289                                     avp.getAlignViewport().isNucleotide());
290
291             stringify(dbtoviewBit, savedProjects, nextxref, avp);
292             xrptypes.put(nextxref, _xrptypes);
293
294           }
295
296           // now do the second xref pass starting from either saved or just
297           // recovered split pane, in sequence
298           do // retrieve second set of cross refs or recover and verify
299           {
300             firstcr_ap = 0;
301             for (AlignmentViewPanel avp : cra_views)
302             {
303               nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}";
304               for (String xrefdb : xrptypes.get(nextxref))
305               {
306                 List<AlignmentViewPanel> cra_views2 = new ArrayList<>();
307                 int q = 0;
308                 String nextnextxref = nextxref + " -> " + xrefdb + "{" + q
309                         + "}";
310
311                 if (pass3 == 0)
312                 {
313                   SequenceI[] xrseqs = avp.getAlignment()
314                           .getSequencesArray();
315                   AlignFrame nextaf = Desktop
316                           .getAlignFrameFor(avp.getAlignViewport());
317
318                   cra = CrossRefAction.getHandlerFor(xrseqs,
319                           avp.getAlignViewport().isNucleotide(), xrefdb,
320                           nextaf);
321                   cra.run();
322                   cra_views2 = (List<AlignmentViewPanel>) PA.getValue(cra,
323                           "xrefViews");
324                   if (cra_views2.size() == 0)
325                   {
326                     failedXrefMenuItems.add("No crossrefs retrieved for '"
327                             + nextxref + "' to " + xrefdb + " via '"
328                             + nextaf.getTitle() + "'");
329                     continue;
330                   }
331                   assertNucleotide(cra_views2.get(0),
332                           "Nucleotide panel included proteins for '"
333                                   + nextxref + "' to " + xrefdb + " via '"
334                                   + nextaf.getTitle() + "'");
335                   assertProtein(cra_views2.get(1),
336                           "Protein panel included nucleotides for '"
337                                   + nextxref + "' to " + xrefdb + " via '"
338                                   + nextaf.getTitle() + "'");
339
340                 }
341                 else
342                 {
343                   Desktop.instance.closeAll_actionPerformed(null);
344                   // recover stored project
345                   File storedProject = savedProjects.get(nextnextxref);
346                   if (storedProject == null)
347                   {
348                     failedProjectRecoveries
349                             .add("Failed to store a view for '"
350                                     + nextnextxref + "'");
351                     continue;
352                   }
353                   AlignFrame af2 = new FileLoader(false)
354                           .LoadFileWaitTillLoaded(savedProjects
355                                   .get(nextnextxref).toString(),
356                                   DataSourceType.FILE);
357                   System.out
358                           .println("Recovered view for '" + nextnextxref
359                                   + "' from '" + savedProjects
360                                           .get(nextnextxref).toString()
361                                   + "'");
362                   // gymnastics to recover the alignPanel/Complementary
363                   // alignPanel
364                   if (af2.getViewport().isNucleotide())
365                   {
366                     // top view, then bottom
367                     cra_views2.add(af2.getViewport().getAlignPanel());
368                     cra_views2.add(((jalview.gui.AlignViewport) af2
369                             .getViewport().getCodingComplement())
370                                     .getAlignPanel());
371
372                   }
373                   else
374                   {
375                     // bottom view, then top
376                     cra_views2.add(((jalview.gui.AlignViewport) af2
377                             .getViewport().getCodingComplement())
378                                     .getAlignPanel());
379                     cra_views2.add(af2.getViewport().getAlignPanel());
380                   }
381                   Assert.assertEquals(cra_views2.size(), 2);
382                   Assert.assertNotNull(cra_views2.get(0));
383                   Assert.assertNotNull(cra_views2.get(1));
384                 }
385
386                 for (AlignmentViewPanel nextavp : cra_views2)
387                 {
388                   nextnextxref = nextxref + " -> " + xrefdb + "{" + q++
389                           + "}";
390
391                   // verify references for this panel
392                   AlignmentTest.assertAlignmentDatasetRefs(
393                           nextavp.getAlignment(),
394                           "" + "Pass (" + pass1 + "," + pass2 + "): For "
395                                   + nextnextxref + ":");
396                   assertDatasetIsNormalisedKnownDefect(
397                           nextavp.getAlignment(),
398                           "" + "Pass (" + pass1 + "," + pass2 + "): For "
399                                   + nextnextxref + ":");
400
401                   stringify(dbtoviewBit, savedProjects, nextnextxref,
402                           nextavp);
403                   keyseq.add(nextnextxref);
404                 }
405               } // end of loop around showing all xrefdb for crossrf2
406
407             } // end of loop around all viewpanels from crossrf1
408           } while (pass2 == 2 && pass3++ < 2);
409           // fetchdb->crossref1->crossref-2->verify for xrefs we
410           // either loop twice when pass2=0, or just once when pass2=1
411           // (recovered project from previous crossref)
412
413         } // end of loop over db-xrefs for crossref-2
414
415         // fetchdb-->crossref1
416         // for each xref we try to retrieve xref, store and verify when
417         // pass1=0, or just retrieve and verify when pass1=1
418       } while (pass1 == 1 && pass2++ < 2);
419       // fetchdb
420       // for each ref we
421       // loop twice: first, do the retrieve, second recover from saved project
422
423       // increment pass counters, so we repeat traversal starting from the
424       // oldest saved project first.
425       if (pass1 == 0)
426       {
427         // verify stored projects for first set of cross references
428         pass1 = 1;
429         // and verify cross-references retrieved from stored projects
430         pass2 = 0;
431         pass3 = 0;
432       }
433       else
434       {
435         pass1++;
436       }
437     } while (pass1 < 3);
438
439     if (failedXrefMenuItems.size() > 0)
440     {
441       for (String s : failedXrefMenuItems)
442       {
443         System.err.println(s);
444       }
445       Assert.fail("Faulty xref menu (" + failedXrefMenuItems.size()
446               + " counts)");
447     }
448     if (failedProjectRecoveries.size() > 0)
449     {
450
451       for (String s : failedProjectRecoveries)
452       {
453         System.err.println(s);
454       }
455       Assert.fail(
456               "Didn't recover projects for some retrievals (did they retrieve ?) ("
457                       + failedProjectRecoveries.size() + " counts)");
458     }
459     if (failedDBRetr.size() > 0)
460     {
461       for (String s : failedProjectRecoveries)
462       {
463         System.err.println(s);
464       }
465       Assert.fail("Didn't retrieve some db refs for checking cross-refs ("
466               + failedDBRetr.size() + " counts)");
467     }
468   }
469
470   private void filterDbRefs(List<String> ptypes, List<String> limit)
471   {
472     if (limit != null)
473     {
474       int p = 0;
475       while (ptypes.size() > p)
476       {
477         if (!limit.contains(ptypes.get(p)))
478         {
479           ptypes.remove(p);
480         }
481         else
482         {
483           p++;
484         }
485       }
486     }
487   }
488
489   /**
490    * wrapper to trap known defect for AH002001 testcase
491    * 
492    * @param alignment
493    * @param string
494    */
495   private void assertDatasetIsNormalisedKnownDefect(AlignmentI al,
496           String message)
497   {
498     try
499     {
500       AlignmentTest.assertDatasetIsNormalised(al, message);
501     } catch (AssertionError ae)
502     {
503       if (!ae.getMessage().endsWith("EMBL|AH002001"))
504       {
505         throw ae;
506       }
507       else
508       {
509         System.out.println("Ignored exception for known defect: JAL-2179 : "
510                 + message);
511       }
512
513     }
514   }
515
516   private void assertProtein(AlignmentViewPanel alignmentViewPanel,
517           String message)
518   {
519     assertType(true, alignmentViewPanel, message);
520   }
521
522   private void assertNucleotide(AlignmentViewPanel alignmentViewPanel,
523           String message)
524   {
525     assertType(false, alignmentViewPanel, message);
526   }
527
528   private void assertType(boolean expectProtein,
529           AlignmentViewPanel alignmentViewPanel, String message)
530   {
531     List<SequenceI> nonType = new ArrayList<>();
532     for (SequenceI sq : alignmentViewPanel.getAlignViewport().getAlignment()
533             .getSequences())
534     {
535       if (sq.isProtein() != expectProtein)
536       {
537         nonType.add(sq);
538       }
539     }
540     if (nonType.size() > 0)
541     {
542       Assert.fail(message + " [ "
543               + (expectProtein ? "nucleotides were " : "proteins were ")
544               + nonType.toString() + " ]");
545     }
546   }
547
548   /**
549    * first time called, record strings derived from alignment and
550    * alignedcodonframes, and save view to a project file. Second time called,
551    * compare strings to existing ones. org.testng.Assert.assertTrue on
552    * stringmatch
553    * 
554    * @param dbtoviewBit
555    *          map between xrefpath and view string
556    * @param savedProjects
557    *          - map from xrefpath to saved project filename (createTempFile)
558    * @param xrefpath
559    *          - xrefpath - unique ID for this context (composed of sequence of
560    *          db-fetch/cross-ref actions preceeding state)
561    * @param avp
562    *          - viewpanel to store (for viewpanels in splitframe, the same
563    *          project should be written for both panels, only one needs
564    *          recovering for comparison on the next stringify call, but each
565    *          viewpanel needs to be called with a distinct xrefpath to ensure
566    *          each one's strings are compared)
567    */
568   private void stringify(Map<String, String> dbtoviewBit,
569           Map<String, File> savedProjects, String xrefpath,
570           AlignmentViewPanel avp)
571   {
572     if (savedProjects != null)
573     {
574       if (savedProjects.get(xrefpath) == null)
575       {
576         // write a project file for this view. On the second pass, this will be
577         // recovered and cross-references verified
578         try
579         {
580           File prfile = File.createTempFile("crossRefTest", ".jvp");
581           AlignFrame af = Desktop.getAlignFrameFor(avp.getAlignViewport());
582           new Jalview2XML(false).saveAlignment(af, prfile.toString(),
583                   af.getTitle());
584           System.out.println("Written view from '" + xrefpath + "' as '"
585                   + prfile.getAbsolutePath() + "'");
586           savedProjects.put(xrefpath, prfile);
587         } catch (IOException q)
588         {
589           Assert.fail("Unexpected IO Exception", q);
590         }
591       }
592       else
593       {
594         System.out.println("Stringify check on view from '" + xrefpath
595                 + "' [ possibly retrieved from '"
596                 + savedProjects.get(xrefpath).getAbsolutePath() + "' ]");
597
598       }
599     }
600
601     StringBuilder sbr = new StringBuilder();
602     sbr.append(avp.getAlignment().toString());
603     sbr.append("\n");
604     sbr.append("<End of alignment>");
605     sbr.append("\n");
606     sbr.append(avp.getAlignment().getDataset());
607     sbr.append("\n");
608     sbr.append("<End of dataset>");
609     sbr.append("\n");
610     int p = 0;
611     if (avp.getAlignment().getCodonFrames() != null)
612     {
613       for (AlignedCodonFrame ac : avp.getAlignment().getCodonFrames())
614       {
615         sbr.append("<AlignedCodonFrame " + p++ + ">");
616         sbr.append("\n");
617         sbr.append(ac.toString());
618         sbr.append("\n");
619       }
620     }
621     String dbt = dbtoviewBit.get(xrefpath);
622     if (dbt == null)
623     {
624       dbtoviewBit.put(xrefpath, sbr.toString());
625     }
626     else
627     {
628       Assert.assertEquals(sbr.toString(), dbt,
629               "stringify mismatch for " + xrefpath);
630     }
631   }
632 }