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