DependenciesTests.java
001 /*
002  *  DependenciesTests.java
003  *  Copyright (c) 1998-2008, The University of Sheffield.
004  *
005  *  This code is from the GATE project (http://gate.ac.uk/) and is free
006  *  software licenced under the GNU General Public License version 3. It is
007  *  distributed without any warranty. For more details see COPYING.txt in the
008  *  top level directory (or at http://gatewiki.sf.net/COPYING.txt).
009  */
010 
011 package gate.yam.depend;
012 
013 import gate.persist.PersistenceException;
014 import gate.util.GateException;
015 import gate.yam.YamFile;
016 import junit.framework.Test;
017 import junit.framework.TestCase;
018 import junit.framework.TestSuite;
019 import org.apache.log4j.Logger;
020 import org.springframework.core.io.FileSystemResource;
021 
022 import java.io.*;
023 import java.nio.channels.FileChannel;
024 import java.util.*;
025 import java.util.regex.Matcher;
026 import java.util.regex.Pattern;
027 
028 /**
029  * Tests for Dependencies   
030  @author Angus Roberts
031  */
032 public class DependenciesTests extends TestCase {
033 
034   /** Logger */
035   static Logger log = Logger.getLogger("gate.yam.depend.DependenciesTests");
036 
037   /**
038    * Matches the names of YamFiles as used in testing, capturing the numeric
039    * part for use as a shorthand. (see @link #shorthandToPaths(String).
040    * YamFiles used in testing are named using a conventionally, as defined in
041    * this pattern.
042    */
043   private static Pattern testFilePattern
044           = Pattern.compile("yam-depends-(\\d+).yam");
045 
046   /**
047    * Some test files come in to versions - their original, and their modified
048    * version. This pattern matches the original, capturing the parts that make
049    * up the name of the actual file as used in tests.
050    */
051   private static Pattern originalTestFilePattern
052           = Pattern.compile("(yam-depends-)ORIGINAL-(\\d+.yam)");
053 
054   /**
055    * A FilenameFilter that accepts yam files used by DependenciesTests.
056    * By convention, filenames are "yam-depends-\d+.yam"
057    */
058   class DependenciesTestsFileFilter implements FilenameFilter {
059     /** Accept a file if it is a yam file used in DependenciesTests*/
060     public boolean accept(File dir, String name) {
061       return testFilePattern.matcher(name).matches();
062     }
063   }
064 
065   /**
066    * A FilenameFilter that accepts original versions of yam files used by
067    * DependenciesTests. By convention, filenames are
068    * "yam-depends-ORIGINAL-\d+.yam"
069    */
070   class DependenciesTestsOriginalFileFilter implements FilenameFilter {
071     /** Accept a file if it is the original version of
072      * a yam file used in DependenciesTests
073      */
074     public boolean accept(File dir, String name) {
075       return originalTestFilePattern.matcher(name).matches();
076     }
077   }
078 
079   /**
080    * The directory that test yam files are found in.
081    */
082   private static File yamDir;
083 
084   /**
085    * By convention, each yam file used in testing is named
086    * "yam-depends-\d+.yam". This Map maps the digit part to the full name.
087    * It is used so that we can refer to sets and graphs of files by just their
088    * digit part.
089    */
090   Map<String, String> yamFileNameMap = new HashMap<String, String>();
091 
092   /**
093    * As for @link #yamFileNameMap, but maps the digit part of the name to the
094    * actual YamFile, post-generate().
095    */
096   Map<String, YamFile> yamFileMap = new HashMap<String, YamFile>();
097 
098   /**
099    * A List of parsed YamFiles that make up a wiki
100    */
101   private List<YamFile> wiki1 = new ArrayList<YamFile>();
102 
103   /**
104    * A Dependencies instance for wiki1
105    */
106   private Dependencies dep1;
107 
108 
109   /** Create a Dependencies test case
110    *
111    @param testName  The name of the test
112    */
113   public DependenciesTests(String testName) {
114     super(testName);
115   }
116 
117 
118 
119   // TODO remove setting of directories to strings - get from where?
120   /**
121    * Set up the tests for Dependencies
122    */
123   protected void setUp() {
124 
125     // set up the directory to which dependencies will be serialized
126     String testDirName = System.getProperty("gate.yam.depend.test.dir");
127     if (testDirName == null) {
128       testDirName = "test/scratch/dependencies";
129     }
130 
131     File testDirFile = new File(testDirName);
132     testDirFile.mkdirs();
133     Dependencies.setSerializationDirectory(testDirFile);
134 
135     // get a directory of yam files for testing
136     String yamDirName = System.getProperty("java.yam.resources.dir");
137     if (yamDirName == null) {
138       yamDirName = this.getClass().getResource("/gate/yam/resources").getFile();
139     }
140 
141     log.info("Getting test yam files from: " + yamDirName);
142 
143     yamDir = new File(yamDirName);
144 
145     // Some of our test files have original and modified versions, named
146     // yam-depends-ORIGINAL-\d+.yam and yam-depends-MODIFIED-\d+.yam
147     // Copy the original versions to their "proper" names for loading
148     File[] originalVersions
149             = yamDir.listFiles(new DependenciesTestsOriginalFileFilter());
150     for(File origFile : originalVersions) {
151       //Work out the name as used in tests
152       Matcher matcher = originalTestFilePattern.matcher(origFile.getName());
153       if (!matcher.matches()) {
154         fail("Failed to setup - filename not conventional: "
155                 + origFile.getName());
156       }
157       String newName = matcher.group(1+ matcher.group(2);
158       File newFile = new File(yamDir, newName);
159       copy(origFile, newFile);
160            
161     }
162 
163     // Get all the yam files used by DependenciesTests
164     File[] testFiles = yamDir.listFiles(new DependenciesTestsFileFilter());
165     for(File file : testFiles ) {
166       YamFile yamFile = YamFile.get(new FileSystemResource(file));
167       try{
168         String canPath = yamFile.getCanonicalPath();
169         log.info("Getting test file: " + canPath);
170         // Group 1 of testFilePattern is the numeric part
171         Matcher matcher = testFilePattern.matcher(file.getName());
172         if(!matcher.matches()) {
173           fail("Failed to setup - filename not conventional: "
174                   + file.getName());        
175         }
176         String number = matcher.group(1);
177         yamFile.generate();        
178         yamFileNameMap.put(number, canPath);
179         yamFileMap.put(number, yamFile);
180       catch(GateException ge) {
181         fail("Failed to setup: " + ge.getMessage());
182       }
183     }
184 
185 
186     // Add some files to a list, to use as a wiki
187     wiki1.add(yamFileMap.get("1"));
188     wiki1.add(yamFileMap.get("2"));
189     wiki1.add(yamFileMap.get("3"));
190     wiki1.add(yamFileMap.get("4"));
191     wiki1.add(yamFileMap.get("5"));
192 
193     // Get dependencies for the wiki
194     try{
195       // Clear it before getting a new one - could have persisted on disk
196       Dependencies.remove("1");
197       dep1 = Dependencies.get("1");
198     catch(PersistenceException pe) {
199       fail("Failed to set up: " + pe.getMessage());
200     }
201     assertTrue("New Dependencies not empty", dep1.isEmpty());
202 
203 
204 
205     // Add all the files in the wiki to the dependencies
206     for(YamFile yam : wiki1) {
207       dep1.created(yam);
208     }
209 
210     assertFalse(
211       "Dependencies is empty after adding links", dep1.isEmpty()
212     );
213 
214     
215   }
216 
217 
218   /**
219    * Test a few Dependencies basics: equality, hashCode, YamFile creation
220    * and removal.
221    @throws Exception if the test fails
222    */
223   public void testCreateAndBasics() throws Exception {
224     log.info("========== DependenciesTests.tesCreateAndBasics() ==============");
225     log.info("testing dependencies.created(YamFile) and equality methods");
226 
227 
228     // Create a Dependencies that is the same as our global dep1
229     Dependencies dep2 = Dependencies.get("2");
230     for(YamFile yam : wiki1) {
231       dep2.created(yam);
232     }
233 
234     // What does this dependencies look like?
235     assertEquals("Dependencies linksTo not correct after create",
236             shorthandToPaths("1:[2,3];2:[4,5];3:[5]"),
237             dep2.linksToAsString());
238 
239     assertEquals("Dependencies linkedBy not correct after create",
240             shorthandToPaths("2:[1];3:[1];4:[2];5:[2,3]"),
241             dep2.linkedByAsString());
242 
243     assertEquals("Dependencies includes not correct after create",
244             shorthandToPaths("1:[2,3,4];2:[4]"),
245             dep2.includesAsString());
246 
247     assertEquals("Dependencies includedBy not correct after create",
248             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
249             dep2.includedByAsString());
250 
251 
252     // Test equality
253     assertEquals("Same Dependencies not equal", dep1, dep1);
254 
255     assertEquals(
256             "Dependencies not equal after get", dep1, Dependencies.get("1"));
257 
258     assertEquals("Identical Dependencies not equal", dep1, dep2);
259 
260     assertEquals(
261             "Identical Dependencies with different hash codes",
262             dep1.hashCode(), dep2.hashCode());
263 
264     // Can we remove a Dependencies?
265     Dependencies.remove("2");
266     assertFalse("Dependencies still exists after removal",
267             Dependencies.exists("2"));
268 
269   }
270 
271   /**
272    * Test Dependencies YamFile deletion
273    @throws Exception if the test fails
274    */
275   public void testDelete() throws Exception {
276     log.info("============== DependenciesTests.testDelete() ==================");
277     log.info("testing dependencies.deleted(YamFile)");
278 
279     // Holds results from Dependencies event operations
280     Set<String> toRegenerate;
281 
282     // What happens if we delete a YamFile?
283     // First, delete one that has links and includes in it
284     toRegenerate = dep1.deleted(yamFileMap.get("1"));
285 
286     assertEquals("Regenerate set not correct after delete",
287             new HashSet<String>(),
288             toRegenerate);
289 
290     assertEquals("Dependencies linksTo not correct after delete",
291             shorthandToPaths("2:[4,5];3:[5]"),
292             dep1.linksToAsString());
293 
294     assertEquals("Dependencies linkedBy not correct after delete",
295             shorthandToPaths("4:[2];5:[2,3]"),
296             dep1.linkedByAsString());
297 
298     assertEquals("Dependencies includes not correct after delete",
299             shorthandToPaths("2:[4]"),
300             dep1.includesAsString());
301 
302     assertEquals("Dependencies includedBy not correct after delete",
303             shorthandToPaths("4:[2]"),
304             dep1.includedByAsString());
305 
306     // Now recreate it - put it back
307     toRegenerate = dep1.created(yamFileMap.get("1"));
308 
309     assertEquals("Regenerate set not correct after create",
310             new HashSet<String>(),
311             toRegenerate);
312 
313 
314     assertEquals("Dependencies linksTo not correct after create",
315             shorthandToPaths("1:[2,3];2:[4,5];3:[5]"),
316             dep1.linksToAsString());
317 
318     assertEquals("Dependencies linkedBy not correct after create",
319             shorthandToPaths("2:[1];3:[1];4:[2];5:[2,3]"),
320             dep1.linkedByAsString());
321 
322     assertEquals("Dependencies includes not correct after create",
323             shorthandToPaths("1:[2,3,4];2:[4]"),
324             dep1.includesAsString());
325 
326     assertEquals("Dependencies includedBy not correct after create",
327             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
328             dep1.includedByAsString());
329 
330   }
331 
332   /**
333    * Test Dependencies YamFile renaming
334    @throws Exception if the test fails
335    */
336   public void testRename() throws Exception {
337     log.info("============== DependenciesTests.testRename() ==================");
338     log.info("testing dependencies.renamed(YamFile)");
339 
340     // Holds results from Dependencies event operations
341     Set<String> toRegenerate;
342     List<String> toRegenerateSorted;
343 
344 
345     // Rename a YamFile. We don't really, just pretend that one file is a rename
346     // of another
347     toRegenerate = dep1.renamed(yamFileMap.get("2"), yamFileMap.get("6"));
348     toRegenerateSorted = new ArrayList<String>(toRegenerate);
349     Collections.sort(toRegenerateSorted);
350 
351     assertEquals("Regenerate set not correct after rename",
352             numbersToList("1"),
353             toRegenerateSorted);
354 
355     assertEquals("Dependencies linksTo not correct after rename",
356             shorthandToPaths("1:[3,6];3:[5];6:[4,5]"),
357             dep1.linksToAsString());
358 
359     assertEquals("Dependencies linkedBy not correct after rename",
360             shorthandToPaths("3:[1];4:[6];5:[3,6];6:[1]"),
361             dep1.linkedByAsString());
362 
363     assertEquals("Dependencies includes not correct after rename",
364             shorthandToPaths("1:[3,4,6];6:[4]"),
365             dep1.includesAsString());
366 
367     assertEquals("Dependencies includedBy not correct after rename",
368             shorthandToPaths("3:[1];4:[1,6];6:[1]"),
369             dep1.includedByAsString());
370 
371     // Put everything back in its original form for further tests
372     dep1.renamed(yamFileMap.get("6"), yamFileMap.get("2"));
373 
374 
375   }
376 
377   /**
378    * Test Dependencies - further YamFile deletion tests
379    @throws Exception if the test fails
380    */
381   public void testDelete2() throws Exception {
382     log.info("============== DependenciesTests.testDelete2() =================");
383     log.info("testing dependencies.deleted(YamFile)");
384 
385     // Holds results from Dependencies event operations
386     Set<String> toRegenerate;
387     List<String> toRegenerateSorted;
388 
389     // Delete a YamFile that will need a bit nore regeneration
390     toRegenerate = dep1.deleted(yamFileMap.get("4"));
391     toRegenerateSorted = new ArrayList<String>(toRegenerate);
392     Collections.sort(toRegenerateSorted);
393 
394     assertEquals("Regenerate set not correct after delete",
395             numbersToList("1,2"),
396             toRegenerateSorted);
397 
398     // And delete another
399     toRegenerate = dep1.deleted(yamFileMap.get("5"));
400     toRegenerateSorted = new ArrayList<String>(toRegenerate);
401     Collections.sort(toRegenerateSorted);
402 
403     assertEquals("Regenerate set not correct after delete",
404             numbersToList("2,3"),
405             toRegenerateSorted);
406 
407     // Put everything back in its original form for further tests
408     dep1.created(yamFileMap.get("4"));
409     dep1.created(yamFileMap.get("5"));
410 
411     // Check all is how it should be
412     assertEquals("Dependencies linksTo not correct after delete and create",
413             shorthandToPaths("1:[2,3];2:[4,5];3:[5]"),
414             dep1.linksToAsString());
415 
416     assertEquals("Dependencies linkedBy not correct after delete and create",
417             shorthandToPaths("2:[1];3:[1];4:[2];5:[2,3]"),
418             dep1.linkedByAsString());
419 
420     assertEquals("Dependencies includes not correct after delete and create",
421             shorthandToPaths("1:[2,3,4];2:[4]"),
422             dep1.includesAsString());
423 
424     assertEquals("Dependencies includedBy not correct after delete and create",
425             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
426             dep1.includedByAsString());
427 
428   }
429   
430   /**
431    * Test Dependencies YamFile modification
432    @throws Exception if the test fails
433    */
434   public void testModify() throws Exception {
435     log.info("============== DependenciesTests.testModify() ==================");
436     log.info("testing dependencies.modified(YamFile)");
437 
438     // Holds results from Dependencies event operations
439     Set<String> toRegenerate;
440     List<String> toRegenerateSorted;
441 
442 
443     // File modification. We fake this by copying a modified version over
444     // an existing file, and re-generating the yam.
445     File currentFile = new File(yamDir, "yam-depends-2.yam");
446     File modFile = new File(yamDir, "yam-depends-MODIFIED-2.yam");
447     copy(modFile, currentFile);
448 
449     // Regenerate the yam
450     yamFileMap.get("2").generate();
451 
452     // Notify the modification
453     toRegenerate = dep1.modified(yamFileMap.get("2"));
454     toRegenerateSorted = new ArrayList<String>(toRegenerate);
455     Collections.sort(toRegenerateSorted);
456 
457     assertEquals("Regenerate set not correct after modify",
458             numbersToList("1"),
459             toRegenerateSorted);
460 
461     // Check all is how it should be
462     assertEquals("Dependencies linksTo not correct after modify",
463             shorthandToPaths("1:[2,3];2:[4,7];3:[5]"),
464             dep1.linksToAsString());
465 
466     assertEquals("Dependencies linkedBy not correct after modify",
467             shorthandToPaths("2:[1];3:[1];4:[2];5:[3];7:[2]"),
468             dep1.linkedByAsString());
469 
470     assertEquals("Dependencies includes not correct after modify",
471             shorthandToPaths("1:[2,3,4];2:[4]"),
472             dep1.includesAsString());
473 
474     assertEquals("Dependencies includedBy not correct after modify",
475             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
476             dep1.includedByAsString());                           
477 
478     // Another File modification.
479     currentFile = new File(yamDir, "yam-depends-1.yam");
480     modFile = new File(yamDir, "yam-depends-MODIFIED-1.yam");
481     copy(modFile, currentFile);
482 
483     // Regenerate the yam
484     yamFileMap.get("1").generate();
485 
486     // Notify the modification
487     toRegenerate = dep1.modified(yamFileMap.get("1"));
488 
489     assertEquals("Regenerate set not correct after modify",
490             new HashSet<String>(),
491             toRegenerate);
492 
493     // Check all is how it should be
494     assertEquals("Dependencies linksTo not correct after modify",
495             shorthandToPaths("1:[2,3];2:[4,7];3:[5]"),
496             dep1.linksToAsString());
497 
498     assertEquals("Dependencies linkedBy not correct after modify",
499             shorthandToPaths("2:[1];3:[1];4:[2];5:[3];7:[2]"),
500             dep1.linkedByAsString());
501 
502     assertEquals("Dependencies includes not correct after modify",
503             shorthandToPaths("1:[3,4];2:[4]"),
504             dep1.includesAsString());
505 
506     assertEquals("Dependencies includedBy not correct after modify",
507             shorthandToPaths("3:[1];4:[1,2]"),
508             dep1.includedByAsString());
509 
510     // Modify all files back
511     copy(new File(yamDir, "yam-depends-ORIGINAL-1.yam"),
512          new File(yamDir, "yam-depends-1.yam"));
513     copy(new File(yamDir, "yam-depends-ORIGINAL-2.yam"),
514          new File(yamDir, "yam-depends-2.yam"));
515 
516     // Regenerate the yams
517     yamFileMap.get("1").generate();
518     yamFileMap.get("2").generate();
519 
520 
521   }
522 
523   /**
524    * Test links to non-yam files and urls
525    @throws Exception if the test fails
526    */
527   public void testNonYamLinks() throws Exception {
528     log.info("============ DependenciesTests.testNonYamLinks() ===============");
529     log.info("testing links to non yam files and urls");
530    
531     // Add a new file to the wiki, create it.
532     YamFile yf = yamFileMap.get("8");
533     wiki1.add(yf);
534     dep1.created(yf);
535 
536     // Check all is how it should be
537     assertEquals("Dependencies linksTo not correct after create",
538             shorthandToPaths("1:[2,3];2:[4,5];3:[5];"
539                     "8:[../../non-existent.html,non-existent.html]"),
540             dep1.linksToAsString());
541 
542     assertEquals("Dependencies linkedBy not correct after create",
543             shorthandToPaths("../../non-existent.html:[8];" "" +
544                     "non-existent.html:[8];2:[1];3:[1];4:[2];5:[2,3]"),
545             dep1.linkedByAsString());
546 
547     assertEquals("Dependencies includes not correct after create",
548             shorthandToPaths("1:[2,3,4];2:[4]"),
549             dep1.includesAsString());
550 
551     assertEquals("Dependencies includedBy not correct after create",
552             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
553             dep1.includedByAsString());
554 
555 
556     // Get rid of the new file
557     dep1.deleted(yf);
558 
559     // Check all is how it should be
560     assertEquals("Dependencies linksTo not correct after delete",
561             shorthandToPaths("1:[2,3];2:[4,5];3:[5]"),
562             dep1.linksToAsString());
563 
564     assertEquals("Dependencies linkedBy not correct after delete",
565             shorthandToPaths("2:[1];3:[1];4:[2];5:[2,3]"),
566             dep1.linkedByAsString());
567 
568     assertEquals("Dependencies includes not correct after delete",
569             shorthandToPaths("1:[2,3,4];2:[4]"),
570             dep1.includesAsString());
571 
572     assertEquals("Dependencies includedBy not correct after delete",
573             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
574             dep1.includedByAsString());
575 
576 
577   }
578 
579 
580   /**
581    * Test Dependencies File creation, deletion, and renaming
582    @throws Exception if the test fails
583    */
584   public void testNonYamChanges() throws Exception {
585     log.info("========= DependenciesTests.testNonYamChanges() =============");
586 
587     // Holds results from Dependencies event operations
588     Set<String> toRegenerate;
589     List<String> toRegenerateSorted;
590 
591     // Create, ie upload, a file
592     log.info("testing dependencies.created(File)");
593     File nonYam = new File(yamDir, "nonYamFile.abc");
594     toRegenerate = dep1.created(nonYam);
595 
596     // Should be nothing to regenerate, nothing links to it
597     assertEquals("Regenerate set not correct after created(File)",
598             new HashSet<String>(),
599             toRegenerate);
600 
601     // Check all is how it should be - no changes
602     assertEquals("Dependencies linksTo not correct after create",
603             shorthandToPaths("1:[2,3];2:[4,5];3:[5]"),
604             dep1.linksToAsString());
605 
606     assertEquals("Dependencies linkedBy not correct after create",
607             shorthandToPaths("2:[1];3:[1];4:[2];5:[2,3]"),
608             dep1.linkedByAsString());
609 
610     assertEquals("Dependencies includes not correct after create",
611             shorthandToPaths("1:[2,3,4];2:[4]"),
612             dep1.includesAsString());
613 
614     assertEquals("Dependencies includedBy not correct after create",
615             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
616             dep1.includedByAsString());
617 
618     // Now delete the non yam
619     toRegenerate = dep1.deleted(nonYam);
620     
621     // Should be nothing to regenerate, nothing links to it
622     assertEquals("Regenerate set not correct after deleted(File)",
623             new HashSet<String>(),
624             toRegenerate);
625 
626     // Now add in a page that links to a non-existent non-yam
627     YamFile linkingFile = yamFileMap.get("9");
628     wiki1.add(linkingFile);
629     toRegenerate = dep1.created(linkingFile);
630 
631     // Should be nothing to regenerate, nothing dependent on 9 has changed
632     assertEquals("Regenerate set not correct after created(YamFile)",
633             new HashSet<String>(),
634             toRegenerate);
635 
636     // Check all is how it should be - a new link to a non yam file
637     assertEquals("Dependencies linksTo not correct after create",
638             shorthandToPaths("1:[2,3];2:[4,5];3:[5];9:[nonYamFile.abc]"),
639             dep1.linksToAsString());
640 
641     assertEquals("Dependencies linkedBy not correct after create",
642             shorthandToPaths("nonYamFile.abc:[9];2:[1];3:[1];4:[2];5:[2,3]"),
643             dep1.linkedByAsString());
644 
645     assertEquals("Dependencies includes not correct after create",
646             shorthandToPaths("1:[2,3,4];2:[4]"),
647             dep1.includesAsString());
648 
649     assertEquals("Dependencies includedBy not correct after create",
650             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
651             dep1.includedByAsString());
652 
653     // Now create the non yam file again
654     toRegenerate = dep1.created(nonYam);
655     toRegenerateSorted = new ArrayList<String>(toRegenerate);
656     Collections.sort(toRegenerateSorted);
657 
658     // We should need to regenerate the web page that links to the non yam
659     assertEquals("Regenerate set not correct after created(File)",
660             numbersToList("9"),
661             toRegenerateSorted);
662 
663     // Rename: we should have to regenerate the linker
664     // We don't really rename a file on the fiesystem, just pretend
665     log.info("testing dependencies.renamed(File, File)");
666     File nonYamRenamed = new File(yamDir, "nonYamFileRenamed.abc");
667     toRegenerate = dep1.renamed(nonYam, nonYamRenamed);
668     toRegenerateSorted = new ArrayList<String>(toRegenerate);
669     Collections.sort(toRegenerateSorted);
670 
671     assertEquals("Regenerate set not correct after renamed(File)",
672             numbersToList("9"),
673             toRegenerateSorted);
674 
675     // Check all is how it should be - link to the renamed file
676     assertEquals("Dependencies linksTo not correct after rename",
677             shorthandToPaths("1:[2,3];2:[4,5];3:[5];9:[nonYamFileRenamed.abc]"),
678             dep1.linksToAsString());
679 
680     assertEquals("Dependencies linkedBy not correct after rename",
681             shorthandToPaths("nonYamFileRenamed.abc:[9];2:[1];3:[1];4:[2]"
682                     ";5:[2,3]"),
683             dep1.linkedByAsString());
684 
685     assertEquals("Dependencies includes not correct after rename",
686             shorthandToPaths("1:[2,3,4];2:[4]"),
687             dep1.includesAsString());
688 
689     assertEquals("Dependencies includedBy not correct after rename",
690             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
691             dep1.includedByAsString());
692 
693     // Delete: we should get some files to regenerate
694     log.info("testing dependencies.deleted(File)");
695     toRegenerate = dep1.deleted(nonYamRenamed);
696     toRegenerateSorted = new ArrayList<String>(toRegenerate);
697     Collections.sort(toRegenerateSorted);
698 
699     assertEquals("Regenerate set not correct after deleted(File)",
700             numbersToList("9"),
701             toRegenerateSorted);
702 
703     // Check all is how it should be - , with a link to a now non-existent file
704     assertEquals("Dependencies linksTo not correct after delete",
705             shorthandToPaths("1:[2,3];2:[4,5];3:[5];9:[nonYamFileRenamed.abc]"),
706             dep1.linksToAsString());
707 
708     assertEquals("Dependencies linkedBy not correct after delete",
709             shorthandToPaths("nonYamFileRenamed.abc:[9];2:[1];3:[1];4:[2]"
710                     ";5:[2,3]"),
711             dep1.linkedByAsString());
712 
713     assertEquals("Dependencies includes not correct after delete",
714             shorthandToPaths("1:[2,3,4];2:[4]"),
715             dep1.includesAsString());
716 
717     assertEquals("Dependencies includedBy not correct after delete",
718             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
719             dep1.includedByAsString());
720 
721     // Remove the linking yam file - should be nothing to regenerate
722     toRegenerate = dep1.deleted(linkingFile);
723     assertEquals("Regenerate set not correct after deleted(YamFile)",
724             new HashSet<String>(),
725             toRegenerate);
726 
727     // ... and we should be back to the beggining
728     assertEquals("Dependencies linksTo not correct after delete",
729             shorthandToPaths("1:[2,3];2:[4,5];3:[5]"),
730             dep1.linksToAsString());
731 
732     assertEquals("Dependencies linkedBy not correct after delete",
733             shorthandToPaths("2:[1];3:[1];4:[2]"
734                     ";5:[2,3]"),
735             dep1.linkedByAsString());
736 
737     assertEquals("Dependencies includes not correct after delete",
738             shorthandToPaths("1:[2,3,4];2:[4]"),
739             dep1.includesAsString());
740 
741     assertEquals("Dependencies includedBy not correct after delete",
742             shorthandToPaths("2:[1];3:[1];4:[1,2]"),
743             dep1.includedByAsString());
744 
745 
746 
747   }
748 
749   /**
750    * Test Dependencies serialization
751    @throws Exception if the test fails
752    */
753   public void testSerialization() throws Exception {
754     log.info("============= DependenciesTests.testSerialization() ============");
755     log.info("testing serialization and deserialization of Dependencies");
756 
757     Dependencies.remove("1");
758     Dependencies dep1 = Dependencies.get("1");
759 
760     // Add all the files in a wiki to the depencies
761     for(YamFile yam : wiki1) {
762       dep1.created(yam);
763     }
764 
765     Dependencies.serialize();
766     Dependencies.clear();
767 
768     assertTrue("Dependencies removed", Dependencies.exists("1"));
769 
770     Dependencies dep1Reloaded = Dependencies.get("1");
771 
772     assertEquals(
773       "Dependencies not consistently serialized / deserialized",
774       dep1, dep1Reloaded
775     );
776 
777     Dependencies.remove("1");
778 
779   }
780 
781 
782   /**
783    * Suite of tests for Dependencies
784    @return The suite of tests
785    */
786   public static Test suite() {
787     TestSuite suite= new TestSuite();    
788     suite.addTest(new DependenciesTests("testCreateAndBasics"));
789     suite.addTest(new DependenciesTests("testDelete"));
790     suite.addTest(new DependenciesTests("testRename"));
791     suite.addTest(new DependenciesTests("testDelete2"));
792     suite.addTest(new DependenciesTests("testModify"));
793     suite.addTest(new DependenciesTests("testNonYamLinks"));
794     suite.addTest(new DependenciesTests("testNonYamChanges"));
795     suite.addTest(new DependenciesTests("testSerialization"));
796     return suite;
797   }
798 
799   /**
800    <p>Replace all file numbers in str with the equivalent canonical paths in
801    * yamFileNameMap. All files used in Dependencies testing are named by
802    * convention as "yam-depends-\d+.yam". Dependencies will report its
803    * internal state graphs in the form "pathA:[pathB,pathC];pathB[pathD]",
804    * where pathX is the canonical path of a file.</p>
805    <p>For test files, we can refer to a Dependencies graph in a shorthand form
806    * using just the digit part of the test name, e.g. "1:[2,3];2[4]" and then
807    * translate this to full canonical paths using this method.</p>
808    <p>Non-yam files are referred to by their path relative to their linking
809    * node. They are replaced by their full canonical path. For example,
810    * "1:[some-file.html]", where some-file is not from yam, will become
811    * "/parent/path/yam-depends-1.yam:[/parent/path/some-file.html]"
812    </p>
813    @param str A string in which we want to replace numbers with test file
814    * canonical paths
815    @return   The equivalent string with numbers replaced by test file
816    * canonical paths
817    */
818   public String shorthandToPaths(String str) {
819 
820     StringBuilder bldr = new StringBuilder();
821 
822     for(String nodeAndArcs : str.split("(\\]\\;)|(\\])")) {
823       String[] nodeAndArcsSplit = nodeAndArcs.split("\\:\\[");
824       String node = nodeAndArcsSplit[0];
825       String arcs = nodeAndArcsSplit[1];
826 
827       String nodePath = null;
828       if(node.matches("\\d+")) {
829         // We've got a number representing a yam file: map it to a filename
830         nodePath = yamFileNameMap.get(node);
831       else {
832         // We've got a string representing a non-yam file
833         // A non-yam node is relative to its sole arc
834         // e.g. abc.html:[2]
835         File yamParent = new File(yamFileNameMap.get(arcs)).getParentFile();        
836         try{
837           nodePath = new File(yamParent, node).getCanonicalPath();          
838         catch(IOException ioe) {
839           fail("Couldn't resolve link canonical path: " + ioe.getMessage());
840         }
841       }
842 
843       bldr.append(nodePath);
844       bldr.append(":[");
845       for(String arc: arcs.split("\\,")) {
846         if(arc.matches("\\d+")) {
847           // We've got a number representing a yam file: map it to a filename
848           bldr.append(yamFileNameMap.get(arc));
849         else {
850           // We've got a string representing a non-yam file
851           // An arc to a non-yam is relative to its node
852           File yamParent = new File(nodePath).getParentFile();
853           File link = new File(yamParent, arc);
854           try{
855           bldr.append(link.getCanonicalPath());
856           catch(IOException ioe) {
857             fail("Couldn't resolve link canonical path: " + ioe.getMessage());
858           }
859         }
860         bldr.append(",");
861       }
862       bldr.deleteCharAt(bldr.length() 1);
863       bldr.append("];");
864     }
865     bldr.deleteCharAt(bldr.length() 1)
866 
867     return bldr.toString();
868   }
869 
870   /**
871    * Replace all file numbers in str with the equivalent canonical paths in
872    * yamFileNameMap, as described in
873    @link DependenciesTests#shorthandToPaths(String).
874    * By convention, the file numbers in str are comma separated
875    @param str A comma separated list of numbers
876    @return A List of canonical paths equivalent to the numbers in str
877    */
878   private List<String> numbersToList(String str) {
879 
880     List<String> pathList = new ArrayList<String>();
881 
882     for(String number : str.split(",")) {
883       pathList.add(yamFileNameMap.get(number));
884     }
885 
886     return pathList;
887   }
888 
889   /**
890    * Copy one File to another. Testing fails if the copy fails.
891    @param in The File that will be copied
892    @param out The File to which in will be copied
893    */
894   private void copy(File in, File out) {
895 
896     log.info("Copying yam file from: " + in.getName()
897               " to: " + out.getName());
898 
899     try {
900       FileChannel ic = new FileInputStream(in).getChannel();
901       FileChannel oc = new FileOutputStream(out).getChannel();
902       ic.transferTo(0, ic.size(), oc);
903       ic.close();
904       oc.close();
905     catch (IOException ioe) {
906       fail("Failed testing while copying modified file: " + ioe.getMessage());
907     }
908   }
909   
910 }