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