diff --git a/.classpath b/.classpath index 16d067f..640b871 100644 --- a/.classpath +++ b/.classpath @@ -2,6 +2,7 @@ + diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF index b478fe5..a2c0561 100644 --- a/META-INF/MANIFEST.MF +++ b/META-INF/MANIFEST.MF @@ -9,7 +9,8 @@ Require-Bundle: org.eclipse.ui, org.strategoxt.strj, org.spoofax.jsglr, org.eclipse.jdt.core, - org.spoofax.terms + org.spoofax.terms, + org.junit Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Export-Package: org.sugarj.common, diff --git a/build.properties b/build.properties index 34d2e4d..237d94f 100644 --- a/build.properties +++ b/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + test/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/src/org/sugarj/common/cleardep/CompilationUnit.java b/src/org/sugarj/common/cleardep/CompilationUnit.java index 4d1963a..8a4e4be 100644 --- a/src/org/sugarj/common/cleardep/CompilationUnit.java +++ b/src/org/sugarj/common/cleardep/CompilationUnit.java @@ -21,7 +21,11 @@ import org.sugarj.util.Pair; /** - * Dependency management for modules. + * Dependency management for modules.
+ *
+ * For each module there are two CompilationUnits. One for automatic + * compilations (generates only temporary files) and one for "real" compilation + * that may use already generated files (if valid) * * @author Sebastian Erdweg */ @@ -37,17 +41,55 @@ public CompilationUnit() { /* for deserialization only */ } protected Synthesizer syn; + /** + * if this == compiledCompilationUnit -> temporary directory
+ * if this == editedCompilationUnit -> i.e. /bin folder + */ protected Path targetDir; + + /** + * source files + */ protected Map sourceArtifacts; + protected Set moduleDependencies; - protected Set circularModuleDependencies; + protected Set circularModuleDependencies; + protected Map externalFileDependencies; + protected Map generatedFiles; // ************************** // Methods for initialization // ************************** + /** + * + * @param cl + * @param stamper + * @param compileDep + * path to the persisted compiledCompilationUnit (.dep file) + * @param compileTarget + * i.e.: /bin folder + * @param editedDep + * path to the persisted editedCompilationUnit (.dep file) + * @param editedTarget + * i.e.: some temporary folder + * @param sourceFiles + * @param editedSourceFiles + * is empty or null, except source has been edited + * @param mode + * + * @param syn + * == null, except for generated modules (which are a product of a + * transformation on another module) + * @return
+ *
    + *
  • edited mode -> editedCompilationUnit + *
  • compiled mode -> compiledCompilationUnit + *
+ * @throws IOException + */ @SuppressWarnings("unchecked") final protected static E create(Class cl, Stamper stamper, Path compileDep, Path compileTarget, Path editedDep, Path editedTarget, Set sourceFiles, Map editedSourceFiles, Mode mode, Synthesizer syn) throws IOException { E compileE; @@ -90,6 +132,22 @@ final protected static E create(Class cl, Stamper return e; } + /** + * a) mode == DoCompileMode (true) checks all files in the compileTarget + * folder (i.e. /bin) and all other dependent modules for consistency + * + * @param cl + * @param stamper + * @param compileDep + * path to the persisted compiledCompilationUnit (.dep file) + * @param editedDep + * path to the persisted editedCompilationUnit (.dep file) + * @param editedSourceFiles + * is empty / null, except source has been edited + * @param mode + * @return + * @throws IOException + */ @SuppressWarnings("unchecked") final protected static Pair read(Class cl, Stamper stamper, Path compileDep, Path editedDep, Map editedSourceFiles, Mode mode) throws IOException { E compileE = PersistableEntity.read(cl, stamper, compileDep); @@ -116,9 +174,15 @@ final protected static Pair read(ClasseditedCompilationUnit to a compiledCompilationUnit + * @param compiled + */ protected void copyContentTo(CompilationUnit compiled) { compiled.sourceArtifacts.putAll(sourceArtifacts); + // TODO: shouldn't the synthesizer also be copied? + for (CompilationUnit dep : moduleDependencies) if (dep.compiledCompilationUnit == null) compiled.addModuleDependency(dep); @@ -138,6 +202,10 @@ protected void copyContentTo(CompilationUnit compiled) { compiled.addGeneratedFile(FileCommands.tryCopyFile(targetDir, compiled.targetDir, p)); } + /** + * Copies contents of all dependent (consistent) editedCompilationUnits to compiledCompilationUnits + * @throws IOException + */ protected void liftEditedToCompiled() throws IOException { ModuleVisitor liftVisitor = new ModuleVisitor() { @Override public Void visit(CompilationUnit mod, Mode mode) { @@ -207,12 +275,29 @@ public void addGeneratedFile(Path file, int stampOfFile) { generatedFiles.put(file, stampOfFile); } + /** + * use addModuleDependency(...) instead + */ + @Deprecated public void addCircularModuleDependency(CompilationUnit mod) { circularModuleDependencies.add(mod); } - public void addModuleDependency(CompilationUnit mod) { + /** + * @return true if mod was added to moduleDependencies
+ * false if mod was added to circularModuleDependencies + */ + public boolean addModuleDependency(final CompilationUnit mod) { + + if (mod.dependsOnTransitively(this) || this.dependsOnTransitively(mod)) { + + circularModuleDependencies.add(mod); + return false; + } + moduleDependencies.add(mod); + return true; + } @@ -220,6 +305,10 @@ public void addModuleDependency(CompilationUnit mod) { // Methods for querying dependencies // ********************************* + /** + * is edited ... + * @return + */ public boolean isParsedCompilationUnit() { return compiledCompilationUnit != null; } @@ -237,16 +326,23 @@ public boolean dependsOnTransitivelyNoncircularly(CompilationUnit other) { return false; } + /** + * Depends on directly + * @param other + * @return + */ public boolean dependsOn(CompilationUnit other) { return moduleDependencies.contains(other) || circularModuleDependencies.contains(other); } public boolean dependsOnTransitively(CompilationUnit other) { + if (dependsOn(other)) return true; for (CompilationUnit mod : moduleDependencies) if (mod.dependsOnTransitively(other)) return true; + return false; } @@ -309,6 +405,12 @@ public Synthesizer getSynthesizer() { protected abstract boolean isConsistentExtend(Mode mode); + /** + * @param editedSourceFiles + * @param mode + * TODO: unused? + * @return + */ protected boolean isConsistentWithSourceArtifacts(Map editedSourceFiles, Mode mode) { if (sourceArtifacts.isEmpty()) return false; @@ -325,6 +427,19 @@ else if (stamp == null && (!FileCommands.exists(e.getKey()) || e.getValue() != s return true; } + /** + * Checks consistency only for this module's ... + *
    + *
  • source files + *
  • generated files + *
  • external file dependencies + *
+ * + * @param editedSourceFiles + * @param mode + * (compiled / edited / ... ) + * @return + */ public boolean isConsistentShallow(Map editedSourceFiles, Mode mode) { if (hasPersistentVersionChanged()) return false; @@ -346,6 +461,14 @@ public boolean isConsistentShallow(Map editedSourceFiles, return true; } + /** + * Checks consistency of this module including its module dependencies
+ * + * @param editedSourceFiles + * @param mode + * (compiled / edited / ... ) + * @return + */ public boolean isConsistent(final Map editedSourceFiles, Mode mode) { ModuleVisitor isConsistentVisitor = new ModuleVisitor() { @Override public Boolean visit(CompilationUnit mod, Mode mode) { return mod.isConsistentShallow(editedSourceFiles, mode); } @@ -411,12 +534,16 @@ private Pair, Map> computeR /** - * Visits the module graph starting from this module, satisfying the following properties: - * - every module transitively imported from `this` module is visited exactly once - * - if a module M1 is visited before a module M2, - * then (i) M1 is not transitively imported from M2 or - * (ii) M1 and M2 transitively have a circular dependency and - * M1 transitively imports M2 using `moduleDependencies` only. + * Visits the module graph starting from this module, satisfying the following + * properties: - every module transitively imported from `this` module is + * visited exactly once - if a module M1 is visited before a module M2, then + * (i) M1 is not transitively imported from M2 or (ii) M1 and M2 transitively + * have a circular dependency and M1 transitively imports M2 using + * `moduleDependencies` only. + * + * Shorter: Visits every module that is transitively imported from 'this' + * module exactly once Visits Module M1 before M2 if M1 is not transitively + * (-noncircularly) imported from M2 */ public T visit(ModuleVisitor visitor) { return visit(visitor, null); } public T visit(ModuleVisitor visitor, Mode thisMode) { diff --git a/src/org/sugarj/common/cleardep/Synthesizer.java b/src/org/sugarj/common/cleardep/Synthesizer.java index cfe694c..1af97e2 100644 --- a/src/org/sugarj/common/cleardep/Synthesizer.java +++ b/src/org/sugarj/common/cleardep/Synthesizer.java @@ -8,12 +8,27 @@ import org.sugarj.common.path.Path; /** + *
    + *
  • Used to dependency-track modules (called A$B) that are a product of a transformation (B) on another module (A) + *
  • Holds references to the required modules (A and B) + *
  • The module that includes the transformed module has only a dependency on A$B + *
  • The CompilationUnit of A$B now contains this Synthesizer (called CompilationUnit.syn) + *
+ * * @author Sebastian Erdweg */ public class Synthesizer { public Set modules; - public Map files; + public Map files; // external file dependencies + /** + * + * @param modules + * required by the module to be synthesized + * @param files + * external file dependencies required by the module to be + * synthesized + */ public Synthesizer(Set modules, Map files) { this.modules = modules; this.files = files; @@ -26,10 +41,13 @@ public Synthesizer(Stamper stamper, Set modules, Set file this.files.put(p, stamper.stampOf(p)); } - public void markSynthesized(CompilationUnit c) { + public void markSynthesized(CompilationUnit synthesizedModule) { for (CompilationUnit m : modules) - c.addModuleDependency(m); + synthesizedModule.addModuleDependency(m); + // TODO: maybe the bug was here, when addModuleDiependency didn't recognize + // cycles + for (Entry e : files.entrySet()) - c.addExternalFileDependency(e.getKey(), e.getValue()); + synthesizedModule.addExternalFileDependency(e.getKey(), e.getValue()); } } diff --git a/test/org/sugarj/common/cleardep/CompilationUnitMock.java b/test/org/sugarj/common/cleardep/CompilationUnitMock.java new file mode 100644 index 0000000..70c8450 --- /dev/null +++ b/test/org/sugarj/common/cleardep/CompilationUnitMock.java @@ -0,0 +1,667 @@ +package org.sugarj.common.cleardep; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sugarj.common.FileCommands; +import org.sugarj.common.cleardep.CompilationUnit.ModuleVisitor; +import org.sugarj.common.cleardep.mode.ForEditorMode; +import org.sugarj.common.cleardep.mode.Mode; +import org.sugarj.common.path.AbsolutePath; +import org.sugarj.common.path.Path; +import org.sugarj.common.path.RelativePath; + +/** + * + * @author Simon Ramstedt + * + */ +public class CompilationUnitMock { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + TestFile sourceFolder; + TestFile compileFolder; + TestFile editedFolder; + + Stamper testStamper = ContentHashStamper.instance; + Mode editorMode = new ForEditorMode(null, true); + TestCompilationUnit m1, m2, m3, m4, m5, m6; + TestFile f1, f2, f3, f4, f5; + Map sourceArtifacts; + + @Before + public void setUp() throws Exception { + + sourceFolder = new TestFile(tempFolder.newFolder("source")); + compileFolder = new TestFile(tempFolder.newFolder("compile")); + editedFolder = new TestFile(tempFolder.newFolder("edited")); + + // test modules + m1 = generateRandomModule(); + m2 = generateRandomModule(); + m3 = generateRandomModule(); + m4 = generateRandomModule(); + m5 = generateRandomModule(); + m6 = generateRandomModule(); + + // test files + f1 = generateRandomFileIn(editedFolder); + f2 = generateRandomFileIn(editedFolder); + f3 = generateRandomFileIn(editedFolder); + f4 = generateRandomFileIn(editedFolder); + f5 = generateRandomFileIn(editedFolder); + + } + + @After + public void tearDown() throws Exception { + + } + + + // Tests + + @Test + public void test_getCircularFileDependencies() throws IOException { + + Assert.assertArrayEquals(new Path[0], m1.getCircularFileDependencies().toArray()); + m1.addExternalFileDependency(f1); + m1.addModuleDependency(m2); + m2.addExternalFileDependency(f1); + Path[] cfd = { f1 }; + Assert.assertArrayEquals(cfd, m1.getCircularFileDependencies().toArray()); + } + + @Test + public void test_dependsOn_1() throws Exception { + + m1.addModuleDependency(m2); // m1 -> m2 + + assertTrue(m1.dependsOnNoncircularly(m2)); + assertTrue(m1.dependsOn(m2)); + assertTrue(m1.dependsOnTransitively(m2)); + + m2.addModuleDependency(m3); // m1 -> m2 -> m3 + + assertTrue(m1.dependsOnTransitivelyNoncircularly(m3)); + assertTrue(m1.dependsOnTransitively(m3)); + + } + + @Test + public void test_dependsOn_2() throws Exception { + + // m1 --> m2 + // m1 <~~ m2 + m1.addModuleDependency(m2); + m2.addModuleDependency(m1); + + assertTrue(m1.dependsOn(m2)); + assertTrue(m1.dependsOnNoncircularly(m2)); + assertFalse(m2.dependsOnNoncircularly(m1)); + + // m1 --> m2 --> m3 + // m1 <~~ m2 + m2.addModuleDependency(m3); + + assertTrue(m1.dependsOnTransitively(m3)); + // assertFalse(m1.dependsOnTransitivelyNoncircularly(m3)); + + // m4 --> m1 --> m2 + // m1 <~~ m2 + m4.addModuleDependency(m1); + + } + + @Test + public void test_dependsOn_3() throws Exception { + + // m1 --> m2 --> m3 + // m2 <~~ m3 + m1.addModuleDependency(m2); + m2.addModuleDependency(m3); + m3.addModuleDependency(m2); + + assertTrue(m1.dependsOnTransitively(m3)); + // assertFalse(m1.dependsOnTransitivelyNoncircularly(m3)); + + } + + @Test + public void test_addModuleDependency() throws Exception { + + m1.addModuleDependency(m2); + m2.addModuleDependency(m3); + m3.addModuleDependency(m1); + assertTrue(m1.circularModuleDependencies.isEmpty()); + assertTrue(m2.circularModuleDependencies.isEmpty()); + assertTrue(m1.moduleDependencies.contains(m2)); + assertTrue(m2.moduleDependencies.contains(m3)); + assertTrue(m3.circularModuleDependencies.contains(m1)); + + // TODO: should this case be covered or rejected by addModuleDependency(..) + // ? + m4.addModuleDependency(m5); + m5.addModuleDependency(m6); + assertFalse(m4.circularModuleDependencies.contains(m6)); + m4.addModuleDependency(m6); + assertTrue(m4.circularModuleDependencies.contains(m6)); + } + + @Test + public void test_isConsistentShallow_1() throws Exception { + + assertTrue(m1.isConsistentShallow(null, editorMode)); + + TestFile f1 = generateRandomFileIn(sourceFolder); + // source artifacts + m1.addSourceArtifact(f1.relativeTo(sourceFolder)); + assertTrue(m1.isConsistentShallow(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistentShallow(null, editorMode)); + + } + + @Test + public void test_isConsistentShallow_2() throws Exception { + + m1.addExternalFileDependency(f1); + assertTrue(m1.isConsistentShallow(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistentShallow(null, editorMode)); + + m2.addExternalFileDependency(f2); + assertTrue(m2.isConsistentShallow(null, editorMode)); + changeContentOf(f2); + assertFalse(m2.isConsistentShallow(null, editorMode)); + + } + + @Test + public void test_isConsistentShallow_3() throws Exception { + + m1.addGeneratedFile(f1); + assertTrue(m1.isConsistentShallow(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistentShallow(null, editorMode)); + + m2.addGeneratedFile(f2); + assertTrue(m2.isConsistentShallow(null, editorMode)); + changeContentOf(f2); + assertFalse(m2.isConsistentShallow(null, editorMode)); + } + + @Test + public void test_isConsistent_1() throws Exception { + + assertTrue(m1.isConsistent(null, editorMode)); + + // shallow dependency + m1.addExternalFileDependency(f1); + assertTrue(m1.isConsistent(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistent(null, editorMode)); + + } + + @Test + public void test_isConsistent_2() throws Exception { + + // m1 -> m2 + + m1.addModuleDependency(m2); + assertTrue(m1.isConsistent(null, editorMode)); + + // m1 -> m2 (m2 inconsistent) + m2.addExternalFileDependency(f1); + assertTrue(m1.isConsistent(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistent(null, editorMode)); + } + + @Test + public void test_isConsistent_3() throws Exception { + + // m1 <-> m2 + m1.addModuleDependency(m2); + m2.addModuleDependency(m1); + assertTrue(m1.isConsistent(null, editorMode)); + + // m1 <-> m2 (m2 inconsistent) + m2.addExternalFileDependency(f1); + assertTrue(m1.isConsistent(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistent(null, editorMode)); + + } + + @Test + public void test_isConsistent_4() throws Exception { + + // m1 -> m2 -> m3 + m1.addModuleDependency(m2); + m2.addModuleDependency(m3); + assertTrue(m1.isConsistent(null, editorMode)); + + // m1 -> m2 -> m3 (m3 inconsistent) + m3.addExternalFileDependency(f1); + assertTrue(m1.isConsistent(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistent(null, editorMode)); + + } + + @Test + public void test_isConsistent_5() throws Exception { + + // m1 -> x (synth) -> m2,m3 + + Set synModules = new HashSet<>(); + synModules.add(m2); + synModules.add(m3); + + Set externalDependencies = new HashSet(); + externalDependencies.add(f1); + + TestCompilationUnit x = generateRandomModule(new Synthesizer(testStamper, synModules, externalDependencies)); + + + m1.addModuleDependency(x); + + + assertTrue(m1.isConsistent(null, editorMode)); + FileCommands.delete(f1); + assertFalse(m1.isConsistent(null, editorMode)); + } + + @Test + public void test_isConsistent_6() throws Exception { + + // m1 -> x (synth) -> m2,m3 (m3 inconsistent) + + Set synModules = new HashSet<>(); + synModules.add(m2); + synModules.add(m3); + + Set externalDependencies = new HashSet(); + externalDependencies.add(f1); + + TestCompilationUnit x = generateRandomModule(new Synthesizer(testStamper, synModules, externalDependencies)); + + + m1.addModuleDependency(x); + + // make m3 inconsistent + m3.addGeneratedFile(f2); + assertTrue(m1.isConsistent(null, editorMode)); + + FileCommands.delete(f2); + assertFalse(m1.isConsistent(null, editorMode)); + } + + @Test + public void test_liftEditedToCompiled() throws IOException { + + TestFile e1CompiledDep = randomPathIn(sourceFolder); + TestFile e2CompiledDep = randomPathIn(sourceFolder); + TestFile e3CompiledDep = randomPathIn(sourceFolder); + + TestFile e1EditedDep = randomPathIn(sourceFolder); + TestFile e2EditedDep = randomPathIn(sourceFolder); + TestFile e3EditedDep = randomPathIn(sourceFolder); + + Set e1SourceFiles = new HashSet(); + + Set e2SourceFiles = new HashSet(); + + Set e3SourceFiles = new HashSet(); + + TestCompilationUnit e1 = TestCompilationUnit.create(TestCompilationUnit.class, testStamper, e1CompiledDep, compileFolder, e1EditedDep, editedFolder, e1SourceFiles, null, new ForEditorMode(null, true), null); + + e1.addExternalFileDependency(f1); + e1.addGeneratedFile(f2); + + TestCompilationUnit e2 = TestCompilationUnit.create(TestCompilationUnit.class, testStamper, e2CompiledDep, compileFolder, e2EditedDep, editedFolder, e2SourceFiles, null, new ForEditorMode(null, true), null); + + e2.addExternalFileDependency(f1); + e2.addExternalFileDependency(f3); + e2.addGeneratedFile(f4); + + TestCompilationUnit e3 = TestCompilationUnit.create(TestCompilationUnit.class, testStamper, e3CompiledDep, compileFolder, e3EditedDep, editedFolder, e3SourceFiles, null, new ForEditorMode(null, true), null); + + e3.addExternalFileDependency(f1); + e3.addGeneratedFile(f2); + e3.addGeneratedFile(f5); + + e1.addModuleDependency(e2); + e2.addModuleDependency(e3); + + e1.liftEditedToCompiled(); + + TestFile cf1 = new TestFile(compileFolder, f1.name()); + TestFile cf2 = new TestFile(compileFolder, f2.name()); + TestFile cf3 = new TestFile(compileFolder, f3.name()); + TestFile cf4 = new TestFile(compileFolder, f4.name()); + TestFile cf5 = new TestFile(compileFolder, f5.name()); + + assertEquals(cf1, f1); + assertEquals(cf2, f2); + assertEquals(cf3, f3); + assertEquals(cf4, f4); + assertEquals(cf5, f5); + + TestCompilationUnit c1 = (TestCompilationUnit) e1.compiledCompilationUnit; + TestCompilationUnit c2 = (TestCompilationUnit) e2.compiledCompilationUnit; + TestCompilationUnit c3 = (TestCompilationUnit) e3.compiledCompilationUnit; + + assertTrue(c1.getModuleDependencies().iterator().next() == c2); + assertTrue(c2.getModuleDependencies().iterator().next() == c3); + + } + + @Test + public void test_liftEditedToCompiled_WithSynthesizer() throws IOException { + + + + m1.addExternalFileDependency(f1); + m1.addGeneratedFile(f2); + + m2.addExternalFileDependency(f1); + m2.addExternalFileDependency(f3); + m2.addGeneratedFile(f4); + + // Synthesizer + Set modules = new HashSet(); + Map files = new HashMap(); + modules.add(m1); + modules.add(m2); + files.put(f5, testStamper.stampOf(f5)); + Synthesizer syn = new Synthesizer(modules, files); + + TestFile compiledDep = randomPathIn(sourceFolder); + TestFile editedDep = randomPathIn(sourceFolder); + Set sourceFiles = new HashSet(); + TestCompilationUnit compilationUnitWithSynthesizer = TestCompilationUnit.create(TestCompilationUnit.class, testStamper, compiledDep, compileFolder, editedDep, editedFolder, sourceFiles, null, new ForEditorMode(null, true), syn); + + compilationUnitWithSynthesizer.liftEditedToCompiled(); + + TestFile cf1 = new TestFile(compileFolder, f1.name()); + TestFile cf2 = new TestFile(compileFolder, f2.name()); + TestFile cf3 = new TestFile(compileFolder, f3.name()); + TestFile cf4 = new TestFile(compileFolder, f4.name()); + TestFile cf5 = new TestFile(compileFolder, f5.name()); + + assertEquals(cf1, f1); + assertEquals(cf2, f2); + assertEquals(cf3, f3); + assertEquals(cf4, f4); + assertEquals(cf5, f5); + + TestCompilationUnit c1 = (TestCompilationUnit) m1.compiledCompilationUnit; + TestCompilationUnit c2 = (TestCompilationUnit) m2.compiledCompilationUnit; + TestCompilationUnit c3 = (TestCompilationUnit) compilationUnitWithSynthesizer.compiledCompilationUnit; + + assertTrue(c3.getModuleDependencies().contains(c1)); + assertTrue(c3.getModuleDependencies().contains(c2)); + + // TODO: Should the synthesizer also be copied? + // Assert.assertNotNull(c3.getSynthesizer()); + // assertTrue(c3.getSynthesizer().modules.contains(c1)); + // assertTrue(c3.getSynthesizer().modules.contains(c2)); + } + + + @Test + public void test_visit_1() { + + // m1 ~~> m2 --> m3 + // m1 <-- m2 + m2.addModuleDependency(m1); + m1.addModuleDependency(m2); + m2.addModuleDependency(m3); + + Object[] expected = { m3, m1, m2 }; + Assert.assertArrayEquals(expected, visitSequence(m1).toArray()); + + } + + @Test + public void test_visit_2() { + + // m1 --> m2 --> m3 + // m1 <~~ m2 + m1.addModuleDependency(m2); + m2.addModuleDependency(m1); + m2.addModuleDependency(m3); + + Object[] expected = { m3, m2, m1 }; + Assert.assertArrayEquals(expected, visitSequence(m1).toArray()); + + } + + @Test + public void test_readWrite() throws IOException { + + TestFile e1CompiledDep = randomPathIn(sourceFolder); + + TestFile e1EditedDep = randomPathIn(sourceFolder); + + Set e1SourceFiles = new HashSet(); + + // Synthesizer + Set modules = new HashSet(); + Map files = new HashMap(); + + modules.add(m2); + + files.put(f5, testStamper.stampOf(f5)); + + Synthesizer syn = new Synthesizer(modules, files); + + TestCompilationUnit e1 = TestCompilationUnit.create(TestCompilationUnit.class, testStamper, e1CompiledDep, compileFolder, e1EditedDep, editedFolder, e1SourceFiles, null, new ForEditorMode(null, true), syn); + + e1.addModuleDependency(m3); + e1.circularModuleDependencies.add(m4); + e1.addExternalFileDependency(f1); + e1.addGeneratedFile(f2); + + e1.write(); + + e1 = null; + e1 = TestCompilationUnit.read(TestCompilationUnit.class, testStamper, e1EditedDep); + + Assert.assertNotNull(e1.getSynthesizer()); + assertEquals(m2.persistentPath.getAbsolutePath(), e1.getSynthesizer().modules.iterator().next().persistentPath.getAbsolutePath()); + + List names = new LinkedList(); + for (CompilationUnit c : e1.getModuleDependencies()) { + names.add(c.getName()); + } + assertTrue(names.contains(m2.getName())); + assertTrue(names.contains(m3.getName())); + + assertEquals(m4.persistentPath.getAbsolutePath(), e1.circularModuleDependencies.iterator().next().persistentPath.getAbsolutePath()); + + List paths = new LinkedList(); + for (Path p : e1.getExternalFileDependencies()) { + paths.add(p.getAbsolutePath()); + } + assertTrue(paths.contains(f5.getAbsolutePath())); + assertTrue(paths.contains(f1.getAbsolutePath())); + + assertEquals(f2.getAbsolutePath(), e1.getGeneratedFiles().iterator().next().getAbsolutePath()); + } + + // Helper methods + + List visitSequence(CompilationUnit start) { + + final List visited = new LinkedList(); + + ModuleVisitor v = new ModuleVisitor() { + + @Override + public Void visit(CompilationUnit mod, Mode mode) { + + visited.add(mod); + + return null; + } + + @Override + public Void init() { + return null; + } + + @Override + public Void combine(Void t1, Void t2) { + return null; + } + + @Override + public boolean cancel(Void t) { + return false; + } + }; + + start.visit(v); + + return visited; + } + + /** + * + * @param length + * @return random string containing only lower case letters + */ + public static String randomString(int length) { + + char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray(); + StringBuilder sb = new StringBuilder(); + Random random = new Random(); + for (int i = 0; i < length; i++) { + char c = chars[random.nextInt(chars.length)]; + sb.append(c); + } + return sb.toString(); + } + + public TestFile randomPathIn(TestFile folder) { + + return new TestFile(folder, randomString(15)); + } + + public TestFile generateRandomFileIn(TestFile folder) throws IOException { + TestFile file = new TestFile(folder, randomString(15), randomString(300)); + + return file; + } + + public static void changeContentOf(TestFile file) throws IOException { + + if (FileCommands.exists(file)) + file.write(randomString(300)); + else + throw new IllegalStateException("File to be changed doesn't exist"); + } + + public TestCompilationUnit generateRandomModule() throws IOException { + return generateRandomModule(null); + } + + public TestCompilationUnit generateRandomModule(Synthesizer synthesizer) throws IOException { + + Set srcFiles = new HashSet(); + srcFiles.add(generateRandomFileIn(sourceFolder).relativeTo(sourceFolder)); + + Map editedSourceFiles = new HashMap(); + + return TestCompilationUnit.create(TestCompilationUnit.class, + testStamper, + randomPathIn(sourceFolder), + compileFolder, + randomPathIn(sourceFolder), + editedFolder, + srcFiles, + editedSourceFiles, + editorMode, + synthesizer); + } + + + // Helper Classes + + public class TestFile extends AbsolutePath { + + public TestFile(AbsolutePath folder, String filename) { + + super(folder.getAbsolutePath() + "/" + filename); + } + public TestFile(AbsolutePath folder, String filename, String content) throws IOException { + + super(folder.getAbsolutePath() + "/" + filename); + write(content); + + } + + public String name() { + + return getFile().getName(); + } + + public RelativePath relativeTo(TestFile folder) { + + return FileCommands.getRelativePath(folder, this); + } + + public TestFile(File f) { + super(f.getAbsolutePath()); + } + + public String read() throws IOException { + return FileCommands.readFileAsString(this); + } + + public void write(String content) throws IOException { + + FileCommands.writeToFile(this, content); + } + + @Override + public boolean equals(Object o) { + + if (o instanceof TestFile) { + try { + if (((TestFile) o).read().equals(this.read())) + return true; + } catch (IOException e) { + e.printStackTrace(); + } + } + + return false; + } + + } + +} diff --git a/test/org/sugarj/common/cleardep/TestCompilationUnit.java b/test/org/sugarj/common/cleardep/TestCompilationUnit.java new file mode 100644 index 0000000..b796f94 --- /dev/null +++ b/test/org/sugarj/common/cleardep/TestCompilationUnit.java @@ -0,0 +1,31 @@ +package org.sugarj.common.cleardep; + +import org.sugarj.common.cleardep.mode.Mode; + +/** + * Test Compilation Unit + * + * @author Simon Ramstedt + * + */ +class TestCompilationUnit extends CompilationUnit{ + + + public TestCompilationUnit() { + + } + +/** + * + */ +private static final long serialVersionUID = 863865445137886655L; + +@Override +protected boolean isConsistentExtend(Mode mode) { + + return true; +} + + + +} \ No newline at end of file