diff --git a/pom.xml b/pom.xml
index c2a3112bd..32c9dd2b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,17 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M7
+
+
+ **/*.java
+
+
+
+
maven-assembly-plugin
@@ -122,10 +133,16 @@
- junit
- junit
- 4.13.2
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.9.1
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.9.1
test
-
\ No newline at end of file
+
diff --git a/src/main/java/org/variantsync/diffdetective/analysis/AnalysisResult.java b/src/main/java/org/variantsync/diffdetective/analysis/AnalysisResult.java
index 548d5faf7..acb3e1dc5 100644
--- a/src/main/java/org/variantsync/diffdetective/analysis/AnalysisResult.java
+++ b/src/main/java/org/variantsync/diffdetective/analysis/AnalysisResult.java
@@ -239,9 +239,13 @@ public static AnalysisResult importFrom(final Path p, final Map customParser = customParsers.get(key);
if (customParser == null) {
diff --git a/src/main/java/org/variantsync/diffdetective/datasets/ParseOptions.java b/src/main/java/org/variantsync/diffdetective/datasets/ParseOptions.java
index b4fc12569..4bb31f557 100644
--- a/src/main/java/org/variantsync/diffdetective/datasets/ParseOptions.java
+++ b/src/main/java/org/variantsync/diffdetective/datasets/ParseOptions.java
@@ -1,6 +1,6 @@
package org.variantsync.diffdetective.datasets;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
/**
* Parse options that should be used when parsing commits and patches within a commit history.
@@ -8,7 +8,7 @@
* @param annotationParser A parser for parsing c preprocessor annotations.
* @author Paul Bittner
*/
-public record ParseOptions(DiffStoragePolicy diffStoragePolicy, DiffNodeParser annotationParser) {
+public record ParseOptions(DiffStoragePolicy diffStoragePolicy, CPPAnnotationParser annotationParser) {
public enum DiffStoragePolicy {
REMEMBER_DIFF,
REMEMBER_STRIPPED_DIFF,
@@ -21,13 +21,13 @@ public enum DiffStoragePolicy {
* @see ParseOptions#Default
* @param annotationParser A parser for parsing c preprocessor annotations.
*/
- public ParseOptions(DiffNodeParser annotationParser) {
+ public ParseOptions(CPPAnnotationParser annotationParser) {
this(Default.diffStoragePolicy, annotationParser);
}
/**
* Creates ParseOptions with the given policy for storing diffs.
- * @see ParseOptions#ParseOptions(DiffStoragePolicy, DiffNodeParser)
+ * @see ParseOptions#ParseOptions(DiffStoragePolicy, CPPAnnotationParser)
* @param diffStoragePolicy Decides if and how unix diffs should be remembered when parsing commits.
*/
public ParseOptions withDiffStoragePolicy(DiffStoragePolicy diffStoragePolicy) {
@@ -36,10 +36,10 @@ public ParseOptions withDiffStoragePolicy(DiffStoragePolicy diffStoragePolicy) {
/**
* Default value for ParseOptions that does not remember parsed unix diffs
- * and uses the default value for the parsing annotations ({@link DiffNodeParser#Default}).
+ * and uses the default value for the parsing annotations ({@link CPPAnnotationParser#Default}).
*/
public static final ParseOptions Default = new ParseOptions(
DiffStoragePolicy.DO_NOT_REMEMBER,
- DiffNodeParser.Default
+ CPPAnnotationParser.Default
);
}
diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java
index 9d6e1fdac..a69f38b8d 100644
--- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java
+++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java
@@ -2,7 +2,6 @@
import org.variantsync.diffdetective.datasets.ParseOptions;
import org.variantsync.diffdetective.datasets.Repository;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.feature.PropositionalFormulaParser;
@@ -14,9 +13,11 @@
* @author Kevin Jedelhauser, Paul Maximilian Bittner
*/
public class Marlin {
- public static final DiffNodeParser ANNOTATION_PARSER = new DiffNodeParser(
- new CPPAnnotationParser(PropositionalFormulaParser.Default, new MarlinCPPDiffLineFormulaExtractor())
- );
+ public static final CPPAnnotationParser ANNOTATION_PARSER =
+ new CPPAnnotationParser(
+ PropositionalFormulaParser.Default,
+ new MarlinCPPDiffLineFormulaExtractor()
+ );
/**
* Clones Marlin from Github.
diff --git a/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java b/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java
index 5bb8bcafe..bd103243f 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java
@@ -12,12 +12,11 @@
* - the corresponding line number after the edit
* @author Paul Bittner
*/
-public class DiffLineNumber {
+public record DiffLineNumber(int inDiff, int beforeEdit, int afterEdit) {
/**
* Index for invalid line numbers.
*/
public static final int InvalidLineNumber = -1;
- public int inDiff, beforeEdit, afterEdit;
/**
* Creates a line number of a diff.
@@ -41,34 +40,26 @@ public static DiffLineNumber Invalid() {
}
/**
- * Creates a copy of the given DiffLineNumber.
- */
- public static DiffLineNumber Copy(final DiffLineNumber other) {
- return new DiffLineNumber(other.inDiff, other.beforeEdit, other.afterEdit);
- }
-
- /**
- * Make this line number become a copy of the given line number.
- * @param other Number to copy. Remains unchanged.
- * @return this
+ * Shifts this line number by adding the given offset.
+ * @param offset value to add to this line number.
+ * @return a new {@code DiffLineNumber} shifted by {@code offset}
*/
- public DiffLineNumber set(final DiffLineNumber other) {
- this.inDiff = other.inDiff;
- this.beforeEdit = other.beforeEdit;
- this.afterEdit = other.afterEdit;
- return this;
+ public DiffLineNumber add(int offset) {
+ return add(offset, DiffType.NON);
}
/**
* Shifts this line number by adding the given offset.
- * @param offset Value to add to this line number.
- * @return this
+ * @param offset value to add to this line number.
+ * @param diffType specifies which components are shifted
+ * @return a new {@code DiffLineNumber} shifted by {@code offset}
*/
- public DiffLineNumber add(int offset) {
- this.inDiff += offset;
- this.beforeEdit += offset;
- this.afterEdit += offset;
- return this;
+ public DiffLineNumber add(int offset, DiffType diffType) {
+ return new DiffLineNumber(
+ inDiff + offset,
+ beforeEdit + (diffType == DiffType.ADD ? 0 : offset),
+ afterEdit + (diffType == DiffType.REM ? 0 : offset)
+ );
}
/**
@@ -78,12 +69,12 @@ public DiffLineNumber add(int offset) {
* Non-existing values will be set to {@link DiffLineNumber#InvalidLineNumber}.
* @param diffType The diff type according to which this line number should be filtered.
*/
- public void as(final DiffType diffType) {
- if (diffType == DiffType.ADD) {
- beforeEdit = InvalidLineNumber;
- } else if (diffType == DiffType.REM) {
- afterEdit = InvalidLineNumber;
- }
+ public DiffLineNumber as(final DiffType diffType) {
+ return new DiffLineNumber(
+ inDiff,
+ diffType == DiffType.ADD ? InvalidLineNumber : beforeEdit,
+ diffType == DiffType.REM ? InvalidLineNumber : afterEdit
+ );
}
@Override
diff --git a/src/main/java/org/variantsync/diffdetective/diff/GitDiffer.java b/src/main/java/org/variantsync/diffdetective/diff/GitDiffer.java
index 7a9b4a16d..3c9bdeaf7 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/GitDiffer.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/GitDiffer.java
@@ -22,10 +22,9 @@
import org.variantsync.diffdetective.diff.difftree.parse.DiffTreeParser;
import org.variantsync.diffdetective.diff.result.CommitDiffResult;
import org.variantsync.diffdetective.diff.result.DiffError;
-import org.variantsync.diffdetective.diff.result.DiffResult;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import org.variantsync.diffdetective.preliminary.GitDiff;
import org.variantsync.diffdetective.util.StringUtils;
-import org.variantsync.functjonal.Result;
import org.variantsync.functjonal.iteration.MappedIterator;
import org.variantsync.functjonal.iteration.SideEffectIterator;
import org.variantsync.functjonal.iteration.Yield;
@@ -308,18 +307,26 @@ private static CommitDiffResult getPatchDiffs(
diffFormatter.format(diffEntry);
final String gitDiff = outputStream.toString(StandardCharsets.UTF_8);
- final Result patchDiff =
- getBeforeFullFile(git, parentCommit, diffEntry.getOldPath()).unwrap()
- .bind(file -> createPatchDiff(
- commitDiff,
- diffEntry,
- gitDiff,
- file,
- parseOptions).unwrap()
+ final String filename = diffEntry.getOldPath();
+ try {
+ final BufferedReader file = getBeforeFullFile(git, parentCommit, filename);
+ final PatchDiff patchDiff =
+ createPatchDiff(
+ commitDiff,
+ diffEntry,
+ gitDiff,
+ file,
+ parseOptions
);
- patchDiff.ifSuccess(commitDiff::addPatchDiff);
- patchDiff.ifFailure(errors::add);
+ commitDiff.addPatchDiff(patchDiff);
+ } catch (IOException e) {
+ Logger.debug(e, "Could not obtain full diff of file " + filename + " before commit " + parentCommit + "!");
+ errors.add(DiffError.COULD_NOT_OBTAIN_FULLDIFF);
+ } catch (DiffParseException e) {
+ errors.add(e.getError());
+ }
+
outputStream.reset();
}
} catch (IOException e) {
@@ -337,13 +344,15 @@ private static CommitDiffResult getPatchDiffs(
* @param gitDiff The git diff of the file that was changed
* @param beforeFullFile The full file before the change
* @return The PatchDiff of the given DiffEntry
+ * @throws DiffParseException if {@code gitDiff} couldn't be parsed
*/
- private static DiffResult createPatchDiff(
+ private static PatchDiff createPatchDiff(
CommitDiff commitDiff,
DiffEntry diffEntry,
String gitDiff,
BufferedReader beforeFullFile,
- final ParseOptions parseOptions) {
+ final ParseOptions parseOptions
+ ) throws DiffParseException {
final Matcher matcher = DIFF_HEADER_PATTERN.matcher(gitDiff);
final String strippedDiff;
if (matcher.find()) {
@@ -353,14 +362,9 @@ private static DiffResult createPatchDiff(
}
final String fullDiff = getFullDiff(beforeFullFile, new BufferedReader(new StringReader(strippedDiff)));
- final DiffResult diffTree = DiffTreeParser.createDiffTree(fullDiff, true, true, parseOptions.annotationParser());
-
-// if (diffTree.isFailure()) {
-// Logger.debug("Something went wrong parsing patch for file {} at commit {}!",
-// diffEntry.getOldPath(), commitDiff.getAbbreviatedCommitHash());
-// }
+ try {
+ DiffTree diffTree = DiffTreeParser.createDiffTree(fullDiff, true, true, parseOptions.annotationParser());
- return diffTree.map(t -> {
// not storing the full diff reduces memory usage by around 40-50%
final String diffToRemember = switch (parseOptions.diffStoragePolicy()) {
case DO_NOT_REMEMBER -> "";
@@ -369,8 +373,14 @@ private static DiffResult createPatchDiff(
case REMEMBER_STRIPPED_DIFF -> strippedDiff;
};
- return new PatchDiff(commitDiff, diffEntry, diffToRemember, t);
- });
+ return new PatchDiff(commitDiff, diffEntry, diffToRemember, diffTree);
+ } catch (DiffParseException e) {
+ // if (diffTree.isFailure()) {
+ // Logger.debug(e, "Something went wrong parsing patch for file {} at commit {}!",
+ // diffEntry.getOldPath(), commitDiff.getAbbreviatedCommitHash());
+ // }
+ throw e;
+ }
}
/**
@@ -435,7 +445,7 @@ public static String getFullDiff(BufferedReader beforeFile, BufferedReader gitDi
* @param filename The name of the file
* @return The full content of the file before the commit
*/
- public static DiffResult getBeforeFullFile(Git git, RevCommit commit, String filename) {
+ public static BufferedReader getBeforeFullFile(Git git, RevCommit commit, String filename) throws IOException {
RevTree tree = commit.getTree();
try (TreeWalk treeWalk = new TreeWalk(git.getRepository())) {
@@ -445,14 +455,12 @@ public static DiffResult getBeforeFullFile(Git git, RevCommit co
// Look for the first file that matches filename.
if (!treeWalk.next()) {
- return DiffResult.Failure(DiffError.COULD_NOT_OBTAIN_FULLDIFF, "Could not obtain full diff of file " + filename + " before commit " + commit + "!");
+ throw new FileNotFoundException("Couldn't find " + filename + " in the commit " + commit);
}
ObjectId objectId = treeWalk.getObjectId(0);
ObjectLoader loader = git.getRepository().open(objectId);
- return DiffResult.Success(new BufferedReader(new InputStreamReader(loader.openStream())));
- } catch (IOException e) {
- return DiffResult.Failure(DiffError.COULD_NOT_OBTAIN_FULLDIFF, "Could not obtain full diff of file " + filename + " before commit " + commit + "!");
+ return new BufferedReader(new InputStreamReader(loader.openStream()));
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java
index e244a16c2..16c7416be 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java
@@ -38,10 +38,8 @@ public class DiffNode {
*/
public final NodeType nodeType;
- private boolean isMultilineMacro = false;
-
- private final DiffLineNumber from = DiffLineNumber.Invalid();
- private final DiffLineNumber to = DiffLineNumber.Invalid();
+ private DiffLineNumber from = DiffLineNumber.Invalid();
+ private DiffLineNumber to = DiffLineNumber.Invalid();
private Node featureMapping;
private List lines;
@@ -100,8 +98,8 @@ public DiffNode(DiffType diffType, NodeType nodeType,
this.diffType = diffType;
this.nodeType = nodeType;
- this.from.set(fromLines);
- this.to.set(toLines);
+ this.from = fromLines;
+ this.to = toLines;
this.featureMapping = featureMapping;
this.lines = lines;
}
@@ -620,6 +618,10 @@ public DiffLineNumber getFromLine() {
return from;
}
+ public void setFromLine(DiffLineNumber from) {
+ this.from = from.as(diffType);
+ }
+
/**
* Returns the end line number of this node's corresponding text block.
* The line number is exclusive (i.e., it points 1 behind the last included line).
@@ -628,6 +630,10 @@ public DiffLineNumber getToLine() {
return to;
}
+ public void setToLine(DiffLineNumber to) {
+ this.to = to.as(diffType);
+ }
+
/**
* Returns the range of line numbers of this node's corresponding source code in the text-based diff.
* @see DiffLineNumber#rangeInDiff
@@ -677,21 +683,6 @@ public List getAllChildren() {
return getChildOrder();
}
- /**
- * Determines if this node represents a multi-line macro.
- * @param isMultilineMacro True iff this node represents a multi-line macro.
- */
- public void setIsMultilineMacro(boolean isMultilineMacro) {
- this.isMultilineMacro = isMultilineMacro;
- }
-
- /**
- * Returns true if this node represents a multi-line macro.
- */
- public boolean isMultilineMacro() {
- return isMultilineMacro;
- }
-
/**
* Returns the full feature mapping formula of this node.
* The feature mapping of an {@link NodeType#IF} node is its {@link DiffNode#getDirectFeatureMapping direct feature mapping}.
@@ -986,7 +977,7 @@ public boolean isAnnotation() {
*/
public int getID() {
// Add one to ensure invalid (negative) line numbers don't cause issues.
- int lineNumber = 1 + from.inDiff;
+ int lineNumber = 1 + from.inDiff();
Assert.assertTrue((lineNumber << 2*ID_OFFSET) >> 2*ID_OFFSET == lineNumber);
int id;
@@ -1123,12 +1114,12 @@ public String toTextDiff() {
public String toString() {
String s;
if (isArtifact()) {
- s = String.format("%s_%s from %d to %d", diffType, nodeType, from.inDiff, to.inDiff);
+ s = String.format("%s_%s from %d to %d", diffType, nodeType, from.inDiff(), to.inDiff());
} else if (isRoot()) {
s = "ROOT";
} else {
s = String.format("%s_%s from %d to %d with \"%s\"", diffType, nodeType,
- from.inDiff, to.inDiff, featureMapping);
+ from.inDiff(), to.inDiff(), featureMapping);
}
return s;
}
@@ -1138,7 +1129,7 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DiffNode diffNode = (DiffNode) o;
- return isMultilineMacro == diffNode.isMultilineMacro && diffType == diffNode.diffType && nodeType == diffNode.nodeType && from.equals(diffNode.from) && to.equals(diffNode.to) && Objects.equals(featureMapping, diffNode.featureMapping) && lines.equals(diffNode.lines);
+ return diffType == diffNode.diffType && nodeType == diffNode.nodeType && from.equals(diffNode.from) && to.equals(diffNode.to) && Objects.equals(featureMapping, diffNode.featureMapping) && lines.equals(diffNode.lines);
}
/**
@@ -1151,6 +1142,6 @@ public boolean equals(Object o) {
*/
@Override
public int hashCode() {
- return Objects.hash(diffType, nodeType, isMultilineMacro, from, to, featureMapping, lines);
+ return Objects.hash(diffType, nodeType, from, to, featureMapping, lines);
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java
index c73b07bef..ac6778fed 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java
@@ -1,12 +1,12 @@
package org.variantsync.diffdetective.diff.difftree;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
import org.variantsync.diffdetective.diff.difftree.parse.DiffTreeParser;
import org.variantsync.diffdetective.diff.difftree.source.PatchFile;
import org.variantsync.diffdetective.diff.difftree.source.PatchString;
import org.variantsync.diffdetective.diff.difftree.traverse.DiffTreeTraversal;
import org.variantsync.diffdetective.diff.difftree.traverse.DiffTreeVisitor;
-import org.variantsync.diffdetective.diff.result.DiffResult;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.util.Assert;
import java.io.BufferedReader;
@@ -55,19 +55,21 @@ public DiffTree(DiffNode root, DiffTreeSource source) {
}
/**
- * Same as {@link DiffTree#fromFile(Path, boolean, boolean, DiffNodeParser)} but with
- * the {@link DiffNodeParser#Default default parser} for the lines in the diff.
+ * Same as {@link DiffTree#fromFile(Path, boolean, boolean, CPPAnnotationParser)} but with
+ * the {@link CPPAnnotationParser#Default default parser} for the lines in the diff.
*/
- public static DiffResult fromFile(final Path p, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines) throws IOException {
- return fromFile(p, collapseMultipleCodeLines, ignoreEmptyLines, DiffNodeParser.Default);
+ public static DiffTree fromFile(final Path p, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines) throws IOException, DiffParseException {
+ return fromFile(p, collapseMultipleCodeLines, ignoreEmptyLines, CPPAnnotationParser.Default);
}
/**
- * Same as {@link DiffTree#fromDiff(String, boolean, boolean, DiffNodeParser)} but with
- * the {@link DiffNodeParser#Default default parser} for the lines in the diff.
+ * Same as {@link DiffTree#fromDiff(String, boolean, boolean, CPPAnnotationParser)} but with
+ * the {@link CPPAnnotationParser#Default default parser} for the lines in the diff.
+ *
+ * @throws DiffParseException if {@code diff} couldn't be parsed
*/
- public static DiffResult fromDiff(final String diff, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines) {
- return fromDiff(diff, collapseMultipleCodeLines, ignoreEmptyLines, DiffNodeParser.Default);
+ public static DiffTree fromDiff(final String diff, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines) throws DiffParseException {
+ return fromDiff(diff, collapseMultipleCodeLines, ignoreEmptyLines, CPPAnnotationParser.Default);
}
/**
@@ -84,10 +86,10 @@ public static DiffResult fromDiff(final String diff, boolean collapseM
* @return A result either containing the parsed DiffTree or an error message in case of failure.
* @throws IOException when the given file could not be read for some reason.
*/
- public static DiffResult fromFile(final Path p, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines, final DiffNodeParser annotationParser) throws IOException {
+ public static DiffTree fromFile(final Path p, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines, final CPPAnnotationParser annotationParser) throws IOException, DiffParseException {
try (BufferedReader file = Files.newBufferedReader(p)) {
- final DiffResult tree = DiffTreeParser.createDiffTree(file, collapseMultipleCodeLines, ignoreEmptyLines, annotationParser);
- tree.unwrap().ifSuccess(t -> t.setSource(new PatchFile(p)));
+ final DiffTree tree = DiffTreeParser.createDiffTree(file, collapseMultipleCodeLines, ignoreEmptyLines, annotationParser);
+ tree.setSource(new PatchFile(p));
return tree;
}
}
@@ -104,10 +106,11 @@ public static DiffResult fromFile(final Path p, boolean collapseMultip
* @param ignoreEmptyLines Set to true if empty lines should not be included in the DiffTree.
* @param annotationParser The parser that is used to parse lines in the diff to {@link DiffNode}s.
* @return A result either containing the parsed DiffTree or an error message in case of failure.
+ * @throws DiffParseException if {@code diff} couldn't be parsed
*/
- public static DiffResult fromDiff(final String diff, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines, final DiffNodeParser annotationParser) {
- final DiffResult tree = DiffTreeParser.createDiffTree(diff, collapseMultipleCodeLines, ignoreEmptyLines, annotationParser);
- tree.unwrap().ifSuccess(t -> t.setSource(new PatchString(diff)));
+ public static DiffTree fromDiff(final String diff, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines, final CPPAnnotationParser annotationParser) throws DiffParseException {
+ final DiffTree tree = DiffTreeParser.createDiffTree(diff, collapseMultipleCodeLines, ignoreEmptyLines, annotationParser);
+ tree.setSource(new PatchString(diff));
return tree;
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java
index a2f917889..54f8dba7c 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java
@@ -1,6 +1,6 @@
package org.variantsync.diffdetective.diff.difftree;
-import java.util.function.Consumer;
+import org.apache.commons.lang3.function.FailableConsumer;
/**
* Type of change made to an artifact (e.g., a line of text in a text-based diff).
@@ -53,12 +53,18 @@ public void matchBeforeAfter(final Runnable ifExistsBefore, final Runnable ifExi
* Runs task on ifExistsBefore if the value existed before the edit (DiffType != ADD).
* Runs task on ifExistsAfter if the value exists after the edit (DiffType != ADD).
* Note: Runs task on both arguments sequentially if the artifact was not edited (DiffType == NON).
+ * Some tasks may not be run if a task throws an exception.
*
* @param ifExistsBefore Argument that is valid if the diff did not add.
* @param ifExistsAfter Argument that is valid if the edit did not remove.
* @param task Task to run with all given arguments that are valid w.r.t. to this DiffType's lifetime.
+ * @throws E iff {@code task} throws {@code E}
*/
- public void matchBeforeAfter(final T ifExistsBefore, final T ifExistsAfter, final Consumer task) {
+ public void matchBeforeAfter(
+ final T ifExistsBefore,
+ final T ifExistsAfter,
+ final FailableConsumer task
+ ) throws E {
if (this != DiffType.ADD) {
task.accept(ifExistsBefore);
}
@@ -85,15 +91,17 @@ public DiffType inverse() {
/**
* Parses the diff type from a line taken from a text-based diff.
* @param line A line in a patch.
- * @return The type of edit of line.
+ * @return The type of edit of line or null if its an invalid diff line.
*/
public static DiffType ofDiffLine(String line) {
if (line.startsWith(ADD.symbol)) {
return ADD;
} else if (line.startsWith(REM.symbol)) {
return REM;
- } else {
+ } else if (line.startsWith(NON.symbol)) {
return NON;
+ } else {
+ return null;
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java
index aaf3a5660..66a6feb85 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java
@@ -1,7 +1,5 @@
package org.variantsync.diffdetective.diff.difftree;
-import org.variantsync.diffdetective.diff.difftree.parse.MultiLineMacroParser;
-
/**
* The type of nodes in a {@link DiffTree}.
* Corresponds to the tau function from our paper.
@@ -34,26 +32,6 @@ public boolean isAnnotation() {
return this != ARTIFACT;
}
- /**
- * Parses the node type from a line taken from a text-based diff.
- * @param line A line in a patch.
- * @return The type of edit of line.
- */
- public static NodeType ofDiffLine(String line) {
- String macro = MultiLineMacroParser.conditionalMacroName(line);
- if (macro != null) {
- if (macro.equals(IF.name)) {
- return IF;
- } else if (macro.equals(ELSE.name)) {
- return ELSE;
- } else if (macro.equals(ELIF.name)) {
- return ELIF;
- }
- }
-
- return ARTIFACT;
- }
-
/**
* Creates a NodeType from its value names.
* @see Enum#name()
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffNodeParser.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffNodeParser.java
deleted file mode 100644
index 179755c49..000000000
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffNodeParser.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.variantsync.diffdetective.diff.difftree.parse;
-
-import org.prop4j.Node;
-import org.variantsync.diffdetective.diff.DiffLineNumber;
-import org.variantsync.diffdetective.diff.difftree.NodeType;
-import org.variantsync.diffdetective.diff.difftree.DiffNode;
-import org.variantsync.diffdetective.diff.difftree.DiffType;
-import org.variantsync.diffdetective.feature.CPPAnnotationParser;
-
-import java.util.ArrayList;
-
-/**
- * A parser that parses a {@link DiffNode}s from a line in a text-based diff.
- * @param annotationParser The parser to use for parsing feature annotations.
- */
-public record DiffNodeParser(CPPAnnotationParser annotationParser) {
- /**
- * The default node parser that uses {@link CPPAnnotationParser#Default}.
- */
- public static final DiffNodeParser Default = new DiffNodeParser(CPPAnnotationParser.Default);
-
- /**
- * Parses the given line from a text-based diff to a DiffNode.
- *
- * @param diffLine The line which the new node represents.
- * @return A DiffNode with a node type, diff type, label, and feature mapping.
- */
- public DiffNode fromDiffLine(String diffLine) throws IllFormedAnnotationException {
- DiffType diffType = DiffType.ofDiffLine(diffLine);
- NodeType nodeType = NodeType.ofDiffLine(diffLine);
- String label = diffLine.isEmpty() ? diffLine : diffLine.substring(1);
- Node featureMapping;
-
- if (nodeType == NodeType.ARTIFACT || nodeType == NodeType.ELSE) {
- featureMapping = null;
- } else {
- featureMapping = annotationParser.parseDiffLine(diffLine);
- }
-
- ArrayList lines = new ArrayList<>();
- lines.add(label);
- return new DiffNode(
- diffType, nodeType,
- DiffLineNumber.Invalid(), DiffLineNumber.Invalid(),
- featureMapping,
- lines);
- }
-}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffTreeParser.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffTreeParser.java
index dcf78b002..cf2eff98e 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffTreeParser.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/DiffTreeParser.java
@@ -1,9 +1,11 @@
package org.variantsync.diffdetective.diff.difftree.parse;
+import org.apache.commons.lang3.function.FailableSupplier;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.tinylog.Logger;
import org.variantsync.diffdetective.datasets.Repository;
import org.variantsync.diffdetective.diff.CommitDiff;
import org.variantsync.diffdetective.diff.DiffLineNumber;
@@ -12,279 +14,467 @@
import org.variantsync.diffdetective.diff.difftree.DiffNode;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
import org.variantsync.diffdetective.diff.difftree.DiffType;
+import org.variantsync.diffdetective.diff.difftree.NodeType;
import org.variantsync.diffdetective.diff.result.DiffError;
-import org.variantsync.diffdetective.diff.result.DiffResult;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.util.Assert;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Stack;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
+import java.util.regex.Pattern;
/**
* Parser that parses {@link DiffTree}s from text-based diffs.
+ *
+ * Note: Weird line continuations and comments can cause misidentification of conditional macros.
+ * The following examples are all correct according to the C11 standard: (comment end is marked by
+ * {@code *\/}):
+ *
+ * /*
+ * #ifdef A
+ * *\/
+ *
+ * #ifdef /*
+ * *\/ A
+ * #endif
+ *
+ * # /**\/ ifdef
+ * #endif
+ *
+ * # \
+ * ifdef
+ * #endif
+ *
*/
public class DiffTreeParser {
/**
- * The same as {@link DiffTreeParser#createDiffTree(BufferedReader, boolean, boolean, DiffNodeParser)}
- * but with the diff given as a single string with linebreaks instead of a BufferedReader.
- * Rethrows IOExceptions as Assertion errors (that do not have to be catched).
+ * One line of a diff.
+ * In contrast to {@link LogicalLine}, this represents a physical line corresponding to a diff
+ * instead of some source code file.
+ *
+ * @param diffType the diff type of this line, may be {@code null} if this line has no valid
+ * diff type
+ * @param content the actual line content without a line delimiter
+ */
+ public record DiffLine(DiffType diffType, String content) {}
+
+ /**
+ * Matches the beginning of conditional macros.
+ * It doesn't match the whole macro name, for example for {@code #ifdef} only {@code "#if"} is
+ * matched and only {@code "if"} is captured.
+ *
+ * Note that this pattern doesn't handle comments between {@code #} and the macro name.
+ */
+ private final static Pattern macroPattern =
+ Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)");
+
+
+ /* Settings */
+
+ /**
+ * Whether to collapse multiple adjacent lines with the same {@code DiffType} to one artifact
+ * node.
+ */
+ final boolean collapseMultipleCodeLines;
+ /**
+ * Whether to add {@code DiffNode}s for empty lines (regardless of their {@code DiffType}).
+ * If {@link collapseMultipleCodeLines} is {@code true} empty lines are also not added to
+ * existing {@code DiffNode}s.
*/
- public static DiffResult createDiffTree(
+ final boolean ignoreEmptyLines;
+ /**
+ * Customization point for how conditional macros are parsed.
+ */
+ final CPPAnnotationParser annotationParser;
+
+
+ /* State */
+
+ /**
+ * A stack containing the current path before the edit from the root of the currently parsed
+ * {@link DiffTree} to the currently parsed {@link DiffNode}.
+ *
+ * The granularity of the {@link DiffTree}s parsed by this class is always lines because line
+ * diffs can't represent different granularities. This implies that there are no nested artifact
+ * nodes in the resulting {@link DiffTree} and therefore {@code beforeStack} will never contain
+ * an artifact node.
+ */
+ private final Stack beforeStack = new Stack<>();
+ /**
+ * A stack containing the current path after the edit from the root of the currently parsed
+ * {@link DiffTree} to the currently parsed {@link DiffNode}.
+ *
+ * See {@link beforeStack} for more explanations.
+ */
+ private final Stack afterStack = new Stack<>();
+
+ /**
+ * The last artifact node which was parsed by {@link parseLine}.
+ * If the last parsed {@code DiffNode} was not an artifact, {@code lastArtifact} is {@code null}.
+ *
+ * This state is used to implement {@link collapseMultipleCodeLines}.
+ */
+ private DiffNode lastArtifact = null;
+
+
+ /**
+ * The same as {@link DiffTreeParser#createDiffTree(BufferedReader, boolean, boolean, CPPAnnotationParser)}
+ * but with the diff given as a single string with line breaks instead of a {@link BufferedReader}.
+ *
+ * @throws DiffParseException if {@code fullDiff} couldn't be parsed
+ */
+ public static DiffTree createDiffTree(
String fullDiff,
boolean collapseMultipleCodeLines,
boolean ignoreEmptyLines,
- DiffNodeParser nodeParser)
- {
+ CPPAnnotationParser annotationParser
+ ) throws DiffParseException {
try {
- return createDiffTree(new BufferedReader(new StringReader(fullDiff)), collapseMultipleCodeLines, ignoreEmptyLines, nodeParser);
+ return createDiffTree(new BufferedReader(new StringReader(fullDiff)), collapseMultipleCodeLines, ignoreEmptyLines, annotationParser);
} catch (IOException e) {
- throw new AssertionError("No actual IO should be performed, because only a StringReader is used");
+ throw new AssertionError("No actual IO should be performed because only a StringReader is used");
}
}
/**
- * Default parsing method for DiffTrees from diffs.
+ * Default parsing method for {@link DiffTree}s from diffs.
* This implementation has options to collapse multiple code lines into one node and to
* discard empty lines.
* This parsing algorithm is described in detail in Sören Viegener's bachelor's thesis.
*
- * @param fullDiff The full diff of a patch obtained from a buffered reader.
- * @param collapseMultipleCodeLines Whether multiple consecutive code lines with the same diff type
- * should be collapsed into a single artifact node.
- * @param ignoreEmptyLines Whether empty lines (no matter if they are added removed
- * or remained unchanged) should be ignored.
- * @param nodeParser The parser to parse individual lines in the diff to DiffNodes.
- * @return A parsed DiffTree upon success or an error indicating why parsing failed.
- * @throws IOException when reading the given BufferedReader fails.
+ * @param fullDiff The full diff of a patch obtained from a buffered reader.
+ * @param collapseMultipleCodeLines Whether multiple consecutive code lines with the same diff
+ * type should be collapsed into a single artifact node.
+ * @param ignoreEmptyLines Whether empty lines (no matter if they are added removed or remained
+ * unchanged) should be ignored.
+ * @param annotationParser The parser to parse conditional macros lines in the diff to
+ * {@link DiffNode}s.
+ * @return A parsed {@link DiffTree} upon success or an error indicating why parsing failed.
+ * @throws IOException when reading from {@code fullDiff} fails.
+ * @throws DiffParseException if an error in the diff or macro syntax is detected
*/
- public static DiffResult createDiffTree(
+ public static DiffTree createDiffTree(
BufferedReader fullDiff,
boolean collapseMultipleCodeLines,
boolean ignoreEmptyLines,
- DiffNodeParser nodeParser) throws IOException
- {
- final List nodes = new ArrayList<>();
- final Stack beforeStack = new Stack<>();
- final Stack afterStack = new Stack<>();
- final DiffLineNumber lineNo = new DiffLineNumber(0, 0, 0);
- final DiffLineNumber lastLineNo = DiffLineNumber.Copy(lineNo);
-
- DiffNode lastArtifact = null;
- final AtomicReference> error = new AtomicReference<>();
- final BiConsumer errorPropagation = (errType, message) -> {
- if (error.get() == null) {
- error.set(DiffResult.Failure(errType, message));
+ CPPAnnotationParser annotationParser
+ ) throws IOException, DiffParseException {
+ return new DiffTreeParser(
+ collapseMultipleCodeLines,
+ ignoreEmptyLines,
+ annotationParser
+ ).parse(() -> {
+ String line = fullDiff.readLine();
+ if (line == null) {
+ return null;
+ } else if (line.length() == 0) {
+ return new DiffLine(null, "");
+ } else {
+ return new DiffLine(DiffType.ofDiffLine(line), line.substring(1));
}
- };
+ });
+ }
- final MultiLineMacroParser mlMacroParser = new MultiLineMacroParser(nodeParser);
+ /**
+ * Parses a variation tree from a source file.
+ * This method is similar to {@link createDiffTree(BufferedReader, boolean, boolean, CPPAnnotationParser)}
+ * but acts as if all lines where unmodified.
+ *
+ * @param file The source code file (not a diff) to be parsed.
+ * @param collapseMultipleCodeLines Whether multiple consecutive artifact lines should be
+ * collapsed into a single artifact node.
+ * @param ignoreEmptyLines Whether empty lines (no matter if they are added removed or remained
+ * unchanged) should be ignored.
+ * @param annotationParser The parser to parse conditional macros.
+ * @return A parsed {@link DiffTree}.
+ * @throws IOException iff {@code file} throws an {@code IOException}
+ * @throws DiffParseException if an error in the diff or macro syntax is detected
+ */
+ public static DiffTree createVariationTree(
+ BufferedReader file,
+ boolean collapseMultipleCodeLines,
+ boolean ignoreEmptyLines,
+ CPPAnnotationParser annotationParser
+ ) throws IOException, DiffParseException {
+ return new DiffTreeParser(
+ collapseMultipleCodeLines,
+ ignoreEmptyLines,
+ annotationParser
+ ).parse(() -> {
+ String line = file.readLine();
+ if (line == null) {
+ return null;
+ } else {
+ if (line.startsWith("+") || line.startsWith("-")) {
+ Logger.warn(
+ "The source file given to createVariationTree contains a plus or " +
+ "minus sign at the start of a line. Please ensure that you are " +
+ "actually parsing a source file and not a diff."
+ );
+ }
- final DiffNode root = DiffNode.createRoot();
+ return new DiffLine(DiffType.NON, line);
+ }
+ });
+ }
+
+ /**
+ * Initializes the parse state.
+ *
+ * @see createDiffTree(BufferedReader, boolean, boolean, CPPAnnotationParser)
+ */
+ private DiffTreeParser(
+ boolean collapseMultipleCodeLines,
+ boolean ignoreEmptyLines,
+ CPPAnnotationParser annotationParser
+ ) {
+ this.collapseMultipleCodeLines = collapseMultipleCodeLines;
+ this.ignoreEmptyLines = ignoreEmptyLines;
+ this.annotationParser = annotationParser;
+ }
+
+ /**
+ * Parses the line diff {@code fullDiff}.
+ *
+ * @param lines should supply successive lines of the diff to be parsed, or {@code null} if
+ * there are no more lines to be parsed.
+ * @return the parsed {@code DiffTree}
+ * @throws IOException iff {@code lines.get()} throws {@code IOException}
+ * @throws DiffParseException if an error in the line diff or the underlying preprocessor syntax
+ * is detected
+ */
+ private DiffTree parse(
+ FailableSupplier lines
+ ) throws IOException, DiffParseException {
+ DiffNode root = DiffNode.createRoot();
beforeStack.push(root);
afterStack.push(root);
- String currentLine;
- for (int i = 0; (currentLine = fullDiff.readLine()) != null; i++) {
- final DiffType diffType = DiffType.ofDiffLine(currentLine);
+ final LogicalLine beforeLine = new LogicalLine();
+ final LogicalLine afterLine = new LogicalLine();
+ boolean isNon = false;
- // count line numbers
- lastLineNo.set(lineNo);
- lineNo.inDiff = i + 1;
- diffType.matchBeforeAfter(() -> ++lineNo.beforeEdit, () -> ++lineNo.afterEdit);
+ DiffLineNumber lineNumber = new DiffLineNumber(0, 0, 0);
+ DiffLine currentDiffLine;
+ while ((currentDiffLine = lines.get()) != null) {
+ final String currentLine = currentDiffLine.content();
// Ignore line if it is empty.
- if (ignoreEmptyLines && (currentLine.isEmpty()
- // substring(1) here because of diff symbol ('+', '-', ' ') at the beginning of a line.
- || currentLine.substring(1).isBlank())) {
+ if (ignoreEmptyLines && currentLine.isBlank()) {
// discard empty lines
continue;
}
- // check if this is a multiline macro
- final ParseResult isMLMacro;
- try {
- isMLMacro = mlMacroParser.consume(lineNo, currentLine, beforeStack, afterStack, nodes);
- } catch (IllFormedAnnotationException e) {
- return DiffResult.Failure(e);
+ final DiffType diffType = currentDiffLine.diffType();
+ if (diffType == null) {
+ throw new DiffParseException(DiffError.INVALID_DIFF, lineNumber.add(1));
}
- switch (isMLMacro.type()) {
- case Success: {
- if (lastArtifact != null) {
- lastArtifact = endCodeBlock(lastArtifact, lastLineNo);
- }
- // This line belongs to a multiline macro and was handled, so go to the next line.
- continue;
- }
- case Error: {
- isMLMacro.onError(errorPropagation);
- return error.get();
- }
- // line is not a mult-line macro so keep going (break the switch statement).
- case NotMyDuty: break;
- }
-
- if ("endif".equals(MultiLineMacroParser.conditionalMacroName(currentLine))) {
- if (lastArtifact != null) {
- lastArtifact = endCodeBlock(lastArtifact, lastLineNo);
- }
-
- final String currentLineFinal = currentLine;
- diffType.matchBeforeAfter(beforeStack, afterStack,
- stack -> {
- // Set corresponding line of now closed annotation.
- // The last block is the first one on the stack.
- endMacroBlock(stack.peek(), lastLineNo, diffType);
-
- // Pop the relevant stacks until an IF node is popped. If there were ELSEs or ELIFs between
- // an IF and an ENDIF, they were placed on the stack and have to be popped now.
- popIf(stack);
-
- if (stack.isEmpty()) {
- errorPropagation.accept(DiffError.ENDIF_WITHOUT_IF, "ENDIF without IF at line \"" + currentLineFinal + "\"!");
- }
- });
- if (error.get() != null) { return error.get(); }
+ lineNumber = lineNumber.add(1, diffType);
+
+ // Do beforeLine and afterLine represent the same unchanged diff line?
+ isNon = diffType == DiffType.NON &&
+ (isNon || (!beforeLine.hasStarted() && !afterLine.hasStarted()));
+
+ // Add the physical line to the logical line.
+ final DiffLineNumber lineNumberFinal = lineNumber;
+ diffType.matchBeforeAfter(beforeLine, afterLine,
+ node -> node.consume(currentLine, lineNumberFinal)
+ );
+
+ // Parse the completed logical line
+ if (isNon && beforeLine.isComplete() && afterLine.isComplete()) {
+ // Only parse it once if beforeLine and afterLine represent the same unchanged
+ // diff line.
+ parseLine(beforeLine, DiffType.NON, lineNumber);
+ beforeLine.reset();
+ afterLine.reset();
} else {
- // This gets the node type and diff type of the current line and creates a node
- // Note that the node is not yet added to the diff tree.
- final DiffNode newNode;
- try {
- newNode = nodeParser.fromDiffLine(currentLine);
- } catch (IllFormedAnnotationException e) {
- return DiffResult.Failure(e);
+ if (beforeLine.isComplete()) {
+ parseLine(beforeLine, DiffType.REM, lineNumber);
+ beforeLine.reset();
}
-
- // collapse multiple code lines
- if (lastArtifact != null) {
- if (collapseMultipleCodeLines && newNode.isArtifact() && lastArtifact.diffType.equals(newNode.diffType)) {
- lastArtifact.addLines(newNode.getLines());
- continue;
- } else {
- lastArtifact = endCodeBlock(lastArtifact, lastLineNo);
- }
- }
-
- newNode.getFromLine().set(lineNo);
- newNode.addBelow(beforeStack.peek(), afterStack.peek());
- nodes.add(newNode);
-
- if (newNode.isArtifact()) {
- lastArtifact = newNode;
- } else {
- // newNode is if, elif or else
- // push the node to the relevant stacks
- diffType.matchBeforeAfter(beforeStack, afterStack, stack ->
- pushNodeToStack(newNode, stack, lastLineNo).onError(errorPropagation)
- );
- if (error.get() != null) { return error.get(); }
+ if (afterLine.isComplete()) {
+ parseLine(afterLine, DiffType.ADD, lineNumber);
+ afterLine.reset();
}
}
}
- if (beforeStack.size() > 1 || afterStack.size() > 1) {
- return DiffResult.Failure(DiffError.NOT_ALL_ANNOTATIONS_CLOSED);
+ if (beforeLine.hasStarted() || afterLine.hasStarted()) {
+ throw new DiffParseException(
+ DiffError.INVALID_LINE_CONTINUATION,
+ lineNumber
+ );
}
- if (lastArtifact != null) {
- lastArtifact = endCodeBlock(lastArtifact, lineNo);
+ if (beforeStack.size() > 1) {
+ throw new DiffParseException(
+ DiffError.NOT_ALL_ANNOTATIONS_CLOSED,
+ beforeStack.peek().getFromLine()
+ );
}
-
- // Invalidate line numbers according to edits.
- // E.g. if a node was added, it had no line number before the edit.
- for (final DiffNode node : nodes) {
- node.getFromLine().as(node.diffType);
- node.getToLine().as(node.diffType);
+ if (afterStack.size() > 1) {
+ throw new DiffParseException(
+ DiffError.NOT_ALL_ANNOTATIONS_CLOSED,
+ afterStack.peek().getFromLine()
+ );
}
- return DiffResult.Success(new DiffTree(root));
+ // Cleanup state
+ beforeStack.clear();
+ afterStack.clear();
+ lastArtifact = null;
+
+ return new DiffTree(root);
}
/**
- * Push the given node to the given stack including some sanity checks.
- * This method also ensures that annotation blocks are closed correctly,
- * by setting ending line numbers.
- * @param newNode The node to push to the given parsing stack.
- * @param stack Describes current the nesting depth of feature annotations.
- * @param lastLineNo The current line number from which's line the given node was parsed.
- * @return Either success or an error in case a sanity check failed.
+ * Parses one logical line and most notably, handles conditional macros.
+ *
+ * @param line a logical line with {@code line.isComplete() == true}
+ * @param diffType whether {@code line} was added, inserted or unchanged
+ * @param lastLineNumber the last physical line of {@code line}
+ * @throws DiffParseException if erroneous preprocessor macros are detected
*/
- static ParseResult pushNodeToStack(
- final DiffNode newNode,
- final Stack stack,
- final DiffLineNumber lastLineNo) {
- if (newNode.isElif() || newNode.isElse()) {
- if (stack.size() == 1) {
- return ParseResult.ERROR(DiffError.ELSE_OR_ELIF_WITHOUT_IF);
- }
+ private void parseLine(
+ final LogicalLine line,
+ final DiffType diffType,
+ final DiffLineNumber lastLineNumber
+ ) throws DiffParseException {
+ final DiffLineNumber fromLine = line.getStartLineNumber().as(diffType);
+ final DiffLineNumber toLine = lastLineNumber.add(1).as(diffType);
+
+ // Is this line a conditional macro?
+ // Note: The following line doesn't handle comments and line continuations correctly.
+ var matcher = macroPattern.matcher(line.getLines().get(0));
+ var conditionalMacroName = matcher.find()
+ ? matcher.group(1)
+ : null;
+
+ if ("endif".equals(conditionalMacroName)) {
+ lastArtifact = null;
+
+ // Do not create a node for ENDIF, but update the line numbers of the closed if-chain
+ // and remove that if-chain from the relevant stacks.
+ diffType.matchBeforeAfter(beforeStack, afterStack, stack ->
+ popIfChain(stack, fromLine)
+ );
+ } else if (collapseMultipleCodeLines
+ && conditionalMacroName == null
+ && lastArtifact != null
+ && lastArtifact.diffType.equals(diffType)
+ && lastArtifact.getToLine().inDiff() == fromLine.inDiff()) {
+ // Collapse consecutive lines if possible.
+ lastArtifact.addLines(line.getLines());
+ lastArtifact.setToLine(toLine);
+ } else {
+ try {
+ NodeType nodeType = NodeType.ARTIFACT;
+ if (conditionalMacroName != null) {
+ try {
+ nodeType = NodeType.fromName(conditionalMacroName);
+ } catch (IllegalArgumentException e) {
+ throw new DiffParseException(DiffError.INVALID_MACRO_NAME, fromLine);
+ }
+ }
- if (stack.peek().isElse()) {
- return ParseResult.ERROR(DiffError.ELSE_AFTER_ELSE);
+ DiffNode newNode = new DiffNode(
+ diffType,
+ nodeType,
+ fromLine,
+ toLine,
+ nodeType == NodeType.ARTIFACT || nodeType == NodeType.ELSE
+ ? null
+ : annotationParser.parseDiffLines(line.getLines()),
+ line.getLines()
+ );
+
+ addNode(newNode);
+ lastArtifact = newNode.isArtifact() ? newNode : null;
+ } catch (IllFormedAnnotationException e) {
+ throw new DiffParseException(e.getType(), fromLine);
}
-
- // set corresponding line of now closed annotation
- endMacroBlock(stack.peek(), lastLineNo, newNode.diffType);
}
-
- stack.push(newNode);
- return ParseResult.SUCCESS;
}
/**
- * Ends a given code block by setting its ending line number correctly.
- * @param block The code block to end.
- * @param lastLineNo The line number of the last parsed line.
- * This should be the line number of the last line in the diff
- * that is part of the given block.
- * @return null
+ * Pop {@code stack} until an IF node is popped.
+ * If there were ELSEs or ELIFs between an IF and an ENDIF, they were placed on the stack and
+ * have to be popped now. The {@link DiffNode#getToLine() end line numbers} are adjusted
+ *
+ * @param stack the stack which should be popped
+ * @param elseLineNumber the first line of the else which causes this IF to be popped
+ * @throws DiffParseException if {@code stack} doesn't contain an IF node
*/
- private static DiffNode endCodeBlock(final DiffNode block, final DiffLineNumber lastLineNo) {
- block.getToLine().set(lastLineNo).add(1);
- return null;
+ private void popIfChain(
+ Stack stack,
+ DiffLineNumber elseLineNumber
+ ) throws DiffParseException {
+ DiffLineNumber previousLineNumber = elseLineNumber;
+ do {
+ DiffNode annotation = stack.peek();
+
+ // Set the line number of now closed annotations to the beginning of the
+ // following annotation.
+ annotation.setToLine(new DiffLineNumber(
+ Math.max(previousLineNumber.inDiff(), annotation.getToLine().inDiff()),
+ stack == beforeStack
+ ? previousLineNumber.beforeEdit()
+ : annotation.getToLine().beforeEdit(),
+ stack == afterStack
+ ? previousLineNumber.afterEdit()
+ : annotation.getToLine().afterEdit()
+ ));
+
+ previousLineNumber = annotation.getFromLine();
+ } while (!stack.pop().isIf());
+
+ if (stack.isEmpty()) {
+ throw new DiffParseException(DiffError.ENDIF_WITHOUT_IF, elseLineNumber);
+ }
}
/**
- * Ends a given macro block by setting its ending line number correctly.
- * @param block The macro block to end.
- * @param lastLineNo The last line number in the diff that is part of the given block.
- * @param diffTypeOfNewBlock The diff type of the upcoming block. This type determines
- * at which times the given block ends.
+ * Adds a fully parsed node into the {@code DiffTree}.
+ * Annotations also pushed to the relevant stacks.
+ *
+ * @param newNode the fully parsed node to be added
+ * @throws DiffParseException if {@code line} erroneous preprocessor macros are detected
*/
- private static void endMacroBlock(final DiffNode block, final DiffLineNumber lastLineNo, final DiffType diffTypeOfNewBlock) {
- // Add 1 because the end line is exclusive, so we have to point one behind the last line we found.
- final DiffLineNumber to = block.getToLine();
- diffTypeOfNewBlock.matchBeforeAfter(
- () -> to.beforeEdit = lastLineNo.beforeEdit + 1,
- () -> to.afterEdit = lastLineNo.afterEdit + 1);
- // Take the highest value ever set as we want to include all lines that are somehow affected by this block.
- to.inDiff = Math.max(to.inDiff, lastLineNo.inDiff + 1);
- }
+ private void addNode(DiffNode newNode) throws DiffParseException {
+ newNode.addBelow(beforeStack.peek(), afterStack.peek());
+
+ if (newNode.isAnnotation()) {
+ // newNode is IF, ELIF or ELSE, so push it to the relevant stacks.
+ newNode.diffType.matchBeforeAfter(beforeStack, afterStack, stack -> {
+ if (newNode.isElif() || newNode.isElse()) {
+ if (stack.size() == 1) {
+ throw new DiffParseException(
+ DiffError.ELSE_OR_ELIF_WITHOUT_IF,
+ newNode.getFromLine()
+ );
+ }
- /**
- * Pop nodes from the given annotation nesting stack until an If or the root was popped.
- * Used to exist if-elif-else chains.
- * @param stack The stack representing the current nesting of annotations.
- * @return The last popped node. This is either an IF node or the root.
- */
- public static DiffNode popIf(final Stack stack) {
- DiffNode popped;
- do {
- // Don't update line numbers of popped nodes here as this already happened.
- popped = stack.pop();
- } while (!popped.isIf());
- return popped;
+ if (stack.peek().isElse()) {
+ throw new DiffParseException(DiffError.ELSE_AFTER_ELSE, newNode.getFromLine());
+ }
+ }
+
+ stack.push(newNode);
+ });
+ }
}
/**
* Parses the given commit of the given repository.
* @param repo The repository from which a commit should be parsed.
* @param commitHash Hash of the commit to parse.
- * @return A CommitDiff describing edits to variability introduced by the given commit relative to its first parent commit.
+ * @return A CommitDiff describing edits to variability introduced by the given commit relative
+ * to its first parent commit.
* @throws IOException when an error occurred.
*/
public static CommitDiff parseCommit(Repository repo, String commitHash) throws IOException {
@@ -312,7 +502,8 @@ public static CommitDiff parseCommit(Repository repo, String commitHash) throws
* @param repo The repository from which a patch should be parsed.
* @param file The file that was edited by the patch.
* @param commitHash The hash of the commit in which the patch was made.
- * @return A PatchDiff describing edits to variability introduced by the given patch relative to the corresponding commit's first parent commit.
+ * @return A PatchDiff describing edits to variability introduced by the given patch relative to
+ * the corresponding commit's first parent commit.
* @throws IOException when an error occurred.
* @throws AssertionError when no such patch exists.
*/
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/LogicalLine.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/LogicalLine.java
new file mode 100644
index 000000000..709ddc612
--- /dev/null
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/LogicalLine.java
@@ -0,0 +1,87 @@
+package org.variantsync.diffdetective.diff.difftree.parse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.variantsync.diffdetective.diff.DiffLineNumber;
+import org.variantsync.diffdetective.util.Assert;
+
+/**
+ * A logical line consisting of multiple physical lines of a text file joined by line continuations.
+ *
+ * The concept of a logical line is used by the C11 standard to refer to one source code line of a
+ * source file after all line continuations have been eliminated. Essentially a logical line is a
+ * string matched by the regex {@code ([^\n]|\\\n)*} where {@code \n} represents the platform
+ * dependent line delimiter. In contrast, a physical line is matched by the regex {@code [^\n]*}.
+ *
+ * @author Benjamin Moosherr
+ */
+class LogicalLine {
+ private List lines;
+ private boolean isContinued;
+ private DiffLineNumber startLineNumber;
+
+ /**
+ * Constructs an empty logical line.
+ */
+ public LogicalLine() {
+ reset();
+ }
+
+ /**
+ * Starts a new, empty logical line.
+ * This especially ensures {@code hasStarted() == false} and {@code isComplete() == false}.
+ */
+ public void reset() {
+ lines = new ArrayList<>();
+ isContinued = false;
+ startLineNumber = DiffLineNumber.Invalid();
+ }
+
+ /**
+ * Adds the physical line {@code line} with the line number {@code lineNumber} to this logical
+ * line.
+ * This must not be called while {@code isComplete()} returns {@code true}. There must be no
+ * new line inside of {@code line}.
+ */
+ public void consume(String line, DiffLineNumber lineNumber) {
+ Assert.assertTrue(!isComplete());
+
+ if (!hasStarted()) {
+ startLineNumber = lineNumber;
+ }
+ lines.add(line);
+ isContinued = line.endsWith("\\");
+ }
+
+ /**
+ * Returns {@code true} iff at least one physical line was {@link consume}d.
+ */
+ public boolean hasStarted() {
+ return !lines.isEmpty();
+ }
+
+ /**
+ * Returns {@code true} iff the current logical is complete and the next physical line belongs
+ * to a new logical line.
+ */
+ public boolean isComplete() {
+ return hasStarted() && !isContinued;
+ }
+
+ /**
+ * Returns the line number of the first {@link consume}d physical line of the current physical
+ * line.
+ */
+ public DiffLineNumber getStartLineNumber() {
+ return startLineNumber;
+ }
+
+ /**
+ * Returns all physical lines {@link consume}d for the current logical line.
+ * The backslashes of line continuations are still part of the strings.
+ */
+ public List getLines() {
+ return lines;
+ }
+}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/MultiLineMacroParser.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/MultiLineMacroParser.java
deleted file mode 100644
index c8dbe1522..000000000
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/MultiLineMacroParser.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package org.variantsync.diffdetective.diff.difftree.parse;
-
-import org.variantsync.diffdetective.diff.DiffLineNumber;
-import org.variantsync.diffdetective.diff.difftree.DiffNode;
-import org.variantsync.diffdetective.diff.difftree.DiffType;
-
-import java.io.BufferedReader;
-import java.util.List;
-import java.util.Stack;
-import java.util.regex.Pattern;
-
-import static org.variantsync.diffdetective.diff.result.DiffError.MLMACRO_WITHIN_MLMACRO;
-
-/**
- * A parser for definitions of multiline macros in text-based diffs.
- * @author Paul Bittner
- */
-public class MultiLineMacroParser {
- /** Matches conditional macros. Note that it doesn't match the whole line or even the whole
- * macro name. For example {@code #ifdef} is also matched, but only {@code "if"} is captured.
- */
- private final static Pattern macroPattern = Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)");
-
- private final DiffNodeParser nodeParser;
-
- private MultilineMacro beforeMLMacro = null;
- private MultilineMacro afterMLMacro = null;
-
- /**
- * Create a new parser that uses the given DiffNodeParser to construct DiffNodes.
- * @param nodeParser Parser to build DiffNodes.
- */
- public MultiLineMacroParser(DiffNodeParser nodeParser) {
- this.nodeParser = nodeParser;
- }
-
- /**
- * Converts a multiline macro to a DiffNode.
- * @param lineNo The end line number of the macro.
- * @param line The last line of the macro.
- * @param macro The macro to finalize.
- * @param diffType The diff type of the produced node.
- * @param nodes The list to add the node to.
- * @return The finalized macro converted to a DiffNode.
- */
- private DiffNode finalizeMLMacro(
- final DiffLineNumber lineNo,
- final String line,
- final MultilineMacro macro,
- final DiffType diffType,
- final List nodes) throws IllFormedAnnotationException {
- macro.addLine(line);
- macro.diffType = diffType;
-
- final DiffNode node = macro.toDiffNode(nodeParser);
- node.getToLine().set(lineNo);
- nodes.add(node);
- return node;
- }
-
- /**
- * Consumes the next line a text-based diff and determines if that line
- * is part of a multi-line macro definition or not.
- * @param lineNo The line number of the currently parsed line.
- * @param line The line to parse.
- * @param beforeStack The current before stack as defined by Sören's algorithm.
- * @param afterStack The current after stack as defined by Sören's algorithm.
- * @param nodes The list of all DiffNodes that where already parsed.
- * @return {@link ParseResult#SUCCESS} if the line was consumed and is part of a multiline macro definition.
- * {@link ParseResult#NOT_MY_DUTY} if the line is not part of a multiline macro definition and was not parsed.
- * The line remains unparsed and should be parsed in another way.
- * {@link ParseResult#ERROR} if an error occurred (e.g., because of a syntax error).
- * @throws IllFormedAnnotationException when {@link MultiLineMacroParser#finalizeMLMacro} fails.
- * @see DiffTreeParser#createDiffTree(BufferedReader, boolean, boolean, DiffNodeParser)
- */
- ParseResult consume(
- final DiffLineNumber lineNo,
- final String line,
- final Stack beforeStack,
- final Stack afterStack,
- final List nodes
- ) throws IllFormedAnnotationException {
- final DiffType diffType = DiffType.ofDiffLine(line);
- final boolean isAdd = diffType == DiffType.ADD;
- final boolean isRem = diffType == DiffType.REM;
-
- if (continuesMultilineDefinition(line)) {
- // If this multiline macro line is a header...
- if (conditionalMacroName(line) != null) {
- // ... create a new multi line macro to complete.
- if (!isAdd) {
- if (beforeMLMacro != null) {
- return ParseResult.ERROR(MLMACRO_WITHIN_MLMACRO, "Found definition of multiline macro within multiline macro at line " + line + "!");
- }
- beforeMLMacro = new MultilineMacro(line, diffType, lineNo, beforeStack.peek(), afterStack.peek());
- }
- if (!isRem) {
- if (afterMLMacro != null) {
- return ParseResult.ERROR(MLMACRO_WITHIN_MLMACRO, "Found definition of multiline macro within multiline macro at line " + line + "!");
- }
- afterMLMacro = new MultilineMacro(line, diffType, lineNo, beforeStack.peek(), afterStack.peek());
- }
- } else { // body
- // ... otherwise, it is a line within a body of a multiline macro. Thus append it.
- if (!isAdd) {
- if (beforeMLMacro == null) {
- /* If this happens (at least) one of this happened
- * 1. Found line of a multiline macro without header at line " + line + "!
- * 2. Backslash in a comment.
- * 3. It is the head of a multiline #define macro that we classify as artifact.
- *
- * As 2 and 3 are most likely we just assume those.
- */
-// return ParseResult.ERROR("Found line of a multiline macro without header at line " + line + "!");
- return ParseResult.NOT_MY_DUTY;
- }
- beforeMLMacro.addLine(line);
- }
- if (!isRem) {
- if (afterMLMacro == null) {
- // see above
-// return ParseResult.ERROR("Found line of a multiline macro without header at line " + line + "!");
- return ParseResult.NOT_MY_DUTY;
- }
- afterMLMacro.addLine(line);
- }
- }
-
- return ParseResult.SUCCESS;
- } else {
- // We either found the ending line of a multiline macro or just a plain line outside of a macro.
- final boolean inBeforeMLMacro = beforeMLMacro != null;
- final boolean inAfterMLMacro = afterMLMacro != null;
-
- // check if last line of a multi macro
- if (inBeforeMLMacro || inAfterMLMacro) {
- if (
- inBeforeMLMacro
- && inAfterMLMacro
- && diffType == DiffType.NON
- && beforeMLMacro.equals(afterMLMacro)) {
- // We have one single end line for two equal multi line macros -> Merge the nodes.
- final DiffNode mlNode = finalizeMLMacro(
- lineNo,
- line,
- beforeMLMacro /* == afterMLMacro */,
- DiffType.NON, nodes);
-
- ParseResult pushResult = DiffTreeParser.pushNodeToStack(mlNode, beforeStack, beforeMLMacro.getLineFrom());
- if (pushResult.isError()) {
- return pushResult;
- }
-
- pushResult = DiffTreeParser.pushNodeToStack(mlNode, afterStack, afterMLMacro.getLineFrom());
- if (pushResult.isError()) {
- return pushResult;
- }
-
- beforeMLMacro = null;
- afterMLMacro = null;
- } else {
- if (inBeforeMLMacro && !isAdd) {
- final DiffNode beforeMLNode = finalizeMLMacro(
- lineNo,
- line,
- beforeMLMacro,
- DiffType.REM, nodes);
-
- final ParseResult pushResult = DiffTreeParser.pushNodeToStack(beforeMLNode, beforeStack, beforeMLMacro.getLineFrom());
- if (pushResult.isError()) {
- return pushResult;
- }
-
- beforeMLMacro = null;
- }
-
- if (inAfterMLMacro && !isRem) {
- final DiffNode afterMLNode = finalizeMLMacro(
- lineNo,
- line,
- afterMLMacro, DiffType.ADD, nodes);
-
- final ParseResult pushResult = DiffTreeParser.pushNodeToStack(afterMLNode, afterStack, afterMLMacro.getLineFrom());
- if (pushResult.isError()) {
- return pushResult;
- }
-
- afterMLMacro = null;
- }
- }
-
- return ParseResult.SUCCESS;
- }
- }
-
- return ParseResult.NOT_MY_DUTY;
- }
-
- /**
- * Checks whether the given line continues the definition of a multiline macro (possibly within a text-based diff).
- * @param line The line to check for continuing a multiline macro definition.
- * @return True iff the line ends with a backslash.
- */
- public static boolean continuesMultilineDefinition(String line) {
- return line.trim().endsWith("\\");
- }
-
- /**
- * Returns the shortened name of a conditional macro.
- *
- * Shortened means it's one of {@code if}, {@code elif}, {@code else} or {@code endif},
- * although the actual macro name may be longer (for example {@code ifdef}).
- *
- * @param line the first line of the macro
- * @return the shortened name of a conditional macro in {@code line} or {@code null} if there
- * is no conditional macro on {@code line}
- */
- public static String conditionalMacroName(String line) {
- var matcher = macroPattern.matcher(line);
- if (matcher.find()) {
- return matcher.group(1);
- } else {
- return null;
- }
- }
-}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/MultilineMacro.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/MultilineMacro.java
deleted file mode 100644
index 4ca5129ed..000000000
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/MultilineMacro.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.variantsync.diffdetective.diff.difftree.parse;
-
-import org.variantsync.diffdetective.diff.DiffLineNumber;
-import org.variantsync.diffdetective.diff.difftree.DiffNode;
-import org.variantsync.diffdetective.diff.difftree.DiffType;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Represents a multiline macro definition in a diff during parsing of a text-based diff.
- * @author Paul Bittner
- */
-public class MultilineMacro {
- private final List lines;
- private final DiffNode beforeParent;
- private final DiffNode afterParent;
- private final DiffLineNumber startLine = DiffLineNumber.Invalid();
- DiffType diffType;
-
- /**
- * Create the definition of a multiline macro from the given values.
- * @param line The corresponding line in the diff.
- * @param diffType The diff type of this multiline macro.
- * @param startLine The line number at which the definition of this macro began.
- * @param beforeParent The parent of this macro before the edit.
- * @param afterParent The parent of this macro after the edit.
- */
- public MultilineMacro(
- final String line,
- final DiffType diffType,
- final DiffLineNumber startLine,
- final DiffNode beforeParent,
- final DiffNode afterParent) {
- this.lines = new ArrayList<>();
- this.lines.add(line);
- this.diffType = diffType;
- this.startLine.set(startLine);
- this.beforeParent = beforeParent;
- this.afterParent = afterParent;
- }
-
- /**
- * Returns the line number at which the definition of this macro began.
- */
- public DiffLineNumber getLineFrom() {
- return startLine;
- }
-
- /**
- * Adds the given line to this multiline macro.
- * The given line is considered to be part of this multiline macro's definition.
- */
- public void addLine(final String line) {
- lines.add(line);
- }
-
- /**
- * Parses this multiline macro definition in a DiffNode.
- * @param nodeParser This parse is used to create the DiffNode after all lines of this
- * multiline macro are joined to a single line.
- * @return A DiffNode representing this multiline macro.
- * @throws IllFormedAnnotationException when {@link DiffNodeParser#fromDiffLine(String)} fails.
- */
- public DiffNode toDiffNode(DiffNodeParser nodeParser) throws IllFormedAnnotationException {
- final StringBuilder asSingleLine = new StringBuilder(diffType.symbol);
-
- for (int l = 0; l < lines.size(); ++l) {
- final String line = lines.get(l);
- if (l < lines.size() - 1) {
- asSingleLine.append(line.substring(1, line.lastIndexOf('\\')).trim()).append(" ");
- } else {
- asSingleLine.append(line.substring(1).trim());
- }
- }
-
- final DiffNode result = nodeParser.fromDiffLine(asSingleLine.toString());
- result.getFromLine().set(startLine);
- result.addBelow(beforeParent, afterParent);
- result.setIsMultilineMacro(true);
- return result;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- MultilineMacro that = (MultilineMacro) o;
- return startLine.equals(that.startLine) && lines.equals(that.lines) && diffType == that.diffType;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(lines, diffType, startLine);
- }
-
- @Override
- public String toString() {
- return "MultilineMacro{" +
- "lines=" + lines +
- ", beforeParent=" + beforeParent +
- ", afterParent=" + afterParent +
- ", startLine=" + startLine +
- ", diffType=" + diffType +
- '}';
- }
-}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/ParseResult.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/ParseResult.java
deleted file mode 100644
index 21bd87101..000000000
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/ParseResult.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.variantsync.diffdetective.diff.difftree.parse;
-
-import org.variantsync.diffdetective.diff.result.DiffError;
-
-import java.util.function.BiConsumer;
-
-/**
- * Result type for parsing.
- * Mostly used for parsing multiline macros.
- * @param type Decides if an element was parsed successfully or not .
- * @param errorType An error description in case an error occurred.
- * @param message A custom message.
- * @see MultiLineMacroParser
- * @author Paul Bittner
- */
-public record ParseResult(ParseResultType type, DiffError errorType, String message) {
- /**
- * Default value for returning that parsing was successful.
- */
- public static final ParseResult SUCCESS = new ParseResult(ParseResultType.Success);
-
- /**
- * Default value for returning that a token was not parsed because
- * it was not relevant for a certain parser.
- * This is mostly used by the {@link MultiLineMacroParser} when it finds a
- * source code line that is not part of a multiline macro definition.
- */
- public static final ParseResult NOT_MY_DUTY = new ParseResult(ParseResultType.NotMyDuty);
-
- /**
- * Creates an error result.
- * @param errorType The type of error that occurred.
- * @param message A custom error message.
- * @return An error result.
- */
- public static ParseResult ERROR(final DiffError errorType, final String message) {
- return new ParseResult(ParseResultType.Error, errorType, message);
- }
-
- /**
- * The same as {@link ParseResult#ERROR(DiffError, String)} but uses the message stored in the given
- * error as the custom message (i.e., {@link DiffError#id()}).
- */
- public static ParseResult ERROR(final DiffError errorType) {
- return ERROR(errorType, errorType.id());
- }
-
- private ParseResult(ParseResultType type) {
- this(type, null, "");
- }
-
- /**
- * Returns true if this result represents an error.
- */
- public boolean isError() {
- return type == ParseResultType.Error;
- }
-
- /**
- * Invokes the given function with the respective error if this
- * result is an error.
- * @param handler Callback to invoke in case an error occured.
- * @return True iff this result is an error.
- * @see ParseResult#isError()
- */
- public boolean onError(BiConsumer handler) {
- if (isError()) {
- handler.accept(errorType, message);
- return true;
- }
-
- return false;
- }
-}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/ParseResultType.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/ParseResultType.java
deleted file mode 100644
index 0599a199c..000000000
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/parse/ParseResultType.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.variantsync.diffdetective.diff.difftree.parse;
-
-/**
- * Result values for reporting parsing results.
- * Used by {@link ParseResult}.
- * @author Paul Bittner
- */
-public enum ParseResultType {
- /**
- * A token (or LOC) could be parsed successfully and was consumed.
- */
- Success,
- /**
- * The parser was not responsible for parsing a token (or LOC) and thus did neither parse
- * nor consume the given token. Another parser should be used.
- */
- NotMyDuty,
- /**
- * A token (or LOC) should have been parsed but parsing failed (e.g., because of an ill-formed string).
- */
- Error
-}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/GraphvizExporter.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/GraphvizExporter.java
index 03ecd733e..de3bf6580 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/GraphvizExporter.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/GraphvizExporter.java
@@ -7,6 +7,7 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.List;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -144,8 +145,9 @@ public InputStream computeGraphvizLayout(
static private String escape(List label) {
return label
.stream()
- .map((line) -> quotePattern.matcher(line).replaceAll((match) -> "\\\\" + match.group()))
+ .map((line) -> quotePattern.matcher(line).replaceAll((match) ->
+ Matcher.quoteReplacement("\\" + match.group())
+ ))
.collect(Collectors.joining("\\n"));
-
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/TikzExporter.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/TikzExporter.java
index fa03a22b2..a46dd13d6 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/TikzExporter.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/TikzExporter.java
@@ -1,13 +1,13 @@
package org.variantsync.diffdetective.diff.difftree.serialize;
import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
import java.io.BufferedReader;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -131,10 +131,10 @@ public void exportDiffTree(
* by exporting directly into a file.
*
* @param diffTree to be exported
- * @param filename of the destination file
+ * @param destination path of the generated file
*/
- public void exportFullLatexExample(DiffTree diffTree, String filename) throws IOException {
- try (var file = new BufferedOutputStream(new FileOutputStream(filename))) {
+ public void exportFullLatexExample(DiffTree diffTree, Path destination) throws IOException {
+ try (var file = Files.newOutputStream(destination)) {
try (var header = new BufferedInputStream(getClass().getResourceAsStream("/tikz_header.tex"))) {
header.transferTo(file);
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/edgeformat/ChildOrderEdgeFormat.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/edgeformat/ChildOrderEdgeFormat.java
new file mode 100644
index 000000000..dc12e04e2
--- /dev/null
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/edgeformat/ChildOrderEdgeFormat.java
@@ -0,0 +1,24 @@
+package org.variantsync.diffdetective.diff.difftree.serialize.edgeformat;
+
+import org.variantsync.diffdetective.diff.difftree.serialize.StyledEdge;
+import org.variantsync.diffdetective.diff.difftree.DiffTree; // For JavaDoc
+
+/**
+ * An edge format encoding the child index of this edge.
+ * The child index is the index of the child to the parent of the edge. If there is no child parent
+ * relationship between the children in a given edge, the child index is -1.
+ *
+ * This index is encoded into decimal and delimited by a semicolon from the previous value.
+ *
+ * This format is mainly useful to equivalence of two {@link DiffTree}s, for example in tests.
+ *
+ * @author Benjamin Moosherr
+ */
+public class ChildOrderEdgeFormat extends EdgeLabelFormat {
+ @Override
+ public String labelOf(StyledEdge edge) {
+ int i = edge.from().indexOfChild(edge.to());
+ int j = edge.to().indexOfChild(edge.from());
+ return String.format(";%d", i < 0 ? j : i);
+ }
+}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FormulasAndLineNumbersNodeFormat.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FormulasAndLineNumbersNodeFormat.java
index 21d47929e..5f31be78d 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FormulasAndLineNumbersNodeFormat.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FormulasAndLineNumbersNodeFormat.java
@@ -13,7 +13,7 @@
public class FormulasAndLineNumbersNodeFormat implements DiffNodeLabelFormat {
@Override
public String toLabel(DiffNode node) {
- final String lineNumbers = node.getFromLine().inDiff + "-" + node.getToLine().inDiff + ": " + node.nodeType;
+ final String lineNumbers = node.getFromLine().inDiff() + "-" + node.getToLine().inDiff() + ": " + node.nodeType;
if (node.isAnnotation()) {
return lineNumbers + " " + node.getDirectFeatureMapping();
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FullNodeFormat.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FullNodeFormat.java
new file mode 100644
index 000000000..c8e4ee82e
--- /dev/null
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/FullNodeFormat.java
@@ -0,0 +1,45 @@
+package org.variantsync.diffdetective.diff.difftree.serialize.nodeformat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.variantsync.diffdetective.diff.difftree.DiffNode;
+
+/**
+ * Labels containing all information encoded into a {@link DiffNode}.
+ *
+ * The format consists of the following lines:
+ *
+ * diffType
+ * nodeType
+ * fromLine
+ * toLine
+ * directFeatureMapping
+ * label
+ * .
+ *
+ * @author Benjamin Moosherr
+ */
+public class FullNodeFormat implements DiffNodeLabelFormat {
+ @Override
+ public List toMultilineLabel(final DiffNode node) {
+ List lines = new ArrayList<>();
+
+ lines.add(node.diffType.toString());
+ lines.add(node.nodeType.toString());
+ lines.add(node.getFromLine().toString());
+ lines.add(node.getToLine().toString());
+ lines.add(node.getDirectFeatureMapping() == null ? "" : node.getDirectFeatureMapping().toString());
+ lines.addAll(node.getLines());
+
+ return lines;
+ }
+
+ @Override
+ public String toLabel(final DiffNode node) {
+ return toMultilineLabel(node)
+ .stream()
+ .collect(Collectors.joining(";"));
+ }
+}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/LineNumberFormat.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/LineNumberFormat.java
index f69618e7a..7c865a1c5 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/LineNumberFormat.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/nodeformat/LineNumberFormat.java
@@ -8,6 +8,6 @@
public class LineNumberFormat implements DiffNodeLabelFormat {
@Override
public String toLabel(final DiffNode node) {
- return String.valueOf(node.getFromLine().inDiff);
+ return String.valueOf(node.getFromLine().inDiff());
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/transform/NaiveMovedArtifactDetection.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/transform/NaiveMovedArtifactDetection.java
index 9882b19ee..492f17be8 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/transform/NaiveMovedArtifactDetection.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/transform/NaiveMovedArtifactDetection.java
@@ -94,8 +94,8 @@ private static DiffNode merge(final DiffNode added, final DiffNode removed) {
final DiffLineNumber addTo = added.getToLine();
final DiffLineNumber remTo = removed.getToLine();
- final DiffLineNumber from = new DiffLineNumber(Math.min(addFrom.inDiff, remFrom.inDiff), remFrom.beforeEdit, addFrom.afterEdit);
- final DiffLineNumber to = new DiffLineNumber(Math.max(addTo.inDiff, remTo.inDiff), remTo.beforeEdit, addTo.afterEdit);
+ final DiffLineNumber from = new DiffLineNumber(Math.min(addFrom.inDiff(), remFrom.inDiff()), remFrom.beforeEdit(), addFrom.afterEdit());
+ final DiffLineNumber to = new DiffLineNumber(Math.max(addTo.inDiff(), remTo.inDiff()), remTo.beforeEdit(), addTo.afterEdit());
return DiffNode.createArtifact(DiffType.NON, from, to, added.getLabel() /* equals removed.getText() */);
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/result/CommitDiffResult.java b/src/main/java/org/variantsync/diffdetective/diff/result/CommitDiffResult.java
index e8098bd56..ea5df3a07 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/result/CommitDiffResult.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/result/CommitDiffResult.java
@@ -1,5 +1,6 @@
package org.variantsync.diffdetective.diff.result;
+import org.tinylog.Logger;
import org.variantsync.diffdetective.diff.CommitDiff;
import java.util.List;
@@ -19,9 +20,10 @@ public record CommitDiffResult(Optional diff, List errors
* @return A failure result that was caused by the given error.
*/
public static CommitDiffResult Failure(DiffError error, String message) {
+ Logger.debug("{}", message);
return new CommitDiffResult(
Optional.empty(),
- List.of(DiffResult.Failure(error, message).unwrap().getFailure())
+ List.of(error)
);
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/result/DiffError.java b/src/main/java/org/variantsync/diffdetective/diff/result/DiffError.java
index f057f10a1..02302d64e 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/result/DiffError.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/result/DiffError.java
@@ -1,61 +1,98 @@
package org.variantsync.diffdetective.diff.result;
+import java.util.Arrays;
+import java.util.Optional;
+
/**
* Describes an error that occurred when processing a text-based diff.
- * @param id error message
*/
-public record DiffError(String id) {
+public enum DiffError {
/**
* A commit has no parents and thus no real diff.
*/
- public final static DiffError COMMIT_HAS_NO_PARENTS = new DiffError("commit has no parents");
+ COMMIT_HAS_NO_PARENTS("commit has no parents"),
/**
* Some internal error occurred when operating JGit.
* The error is not further specified or unknown.
*/
- public final static DiffError JGIT_ERROR = new DiffError("error when operating jgit");
+ JGIT_ERROR("error when operating jgit"),
/**
* An error which occurred when obtaining the full diff from a local diff.
*/
- public final static DiffError COULD_NOT_OBTAIN_FULLDIFF = new DiffError("could not obtain full diff");
+ COULD_NOT_OBTAIN_FULLDIFF("could not obtain full diff"),
/**
* A preprocessor block was opened but not closed.
* Typically, this occurs when an #endif is missing.
*/
- public final static DiffError NOT_ALL_ANNOTATIONS_CLOSED = new DiffError("not all annotations closed");
+ NOT_ALL_ANNOTATIONS_CLOSED("not all annotations closed"),
/**
* A file or patch contained an expression that closes an annotation block (typically an #endif)
* but there is no block to close.
*/
- public final static DiffError ENDIF_WITHOUT_IF = new DiffError("#endif without #if");
+ ENDIF_WITHOUT_IF("#endif without #if"),
/**
* A multiline macro was defined within a multiline macro.
*/
- public final static DiffError MLMACRO_WITHIN_MLMACRO = new DiffError("definition of multiline macro within multiline macro");
+ MLMACRO_WITHIN_MLMACRO("definition of multiline macro within multiline macro"),
/**
* An #else or #elif expression has no corresponding #if (or #ifdef, ...) expression.
*/
- public final static DiffError ELSE_OR_ELIF_WITHOUT_IF = new DiffError("#else or #elif without #if");
+ ELSE_OR_ELIF_WITHOUT_IF("#else or #elif without #if"),
/**
* An #else expression is followed by another #else expression which has no semantics.
*/
- public final static DiffError ELSE_AFTER_ELSE = new DiffError("#else after #else");
+ ELSE_AFTER_ELSE("#else after #else"),
/**
* A condition annotation is missing an expression.
* This typically occurs when an #if macro has no arguments.
*/
- public final static DiffError IF_WITHOUT_CONDITION = new DiffError("conditional macro without expression");
+ IF_WITHOUT_CONDITION("conditional macro without expression"),
+
+ /**
+ * Unknown macro name which was identified as a conditional macro.
+ * Example: {@code #iflol}
+ */
+ INVALID_MACRO_NAME("invalid preprocessor macro name"),
+
+ /**
+ * Empty line in a diff.
+ * All lines in a diff need at least one symbol (the
+ * {@link org.variantsync.diffdetective.diff.difftree.DiffType}) in it.
+ */
+ INVALID_DIFF("missing diff symbol"),
+
+ /**
+ * A line continuation without a following line.
+ */
+ INVALID_LINE_CONTINUATION("a line continuation was detected but there are no more lines");
+
+ private final String message;
+
+ private DiffError(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public static Optional fromMessage(String message) {
+ return Arrays
+ .stream(values())
+ .filter(m -> m.getMessage().equals(message))
+ .findFirst();
+ }
@Override
public String toString() {
- return id;
+ return message;
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/result/DiffParseException.java b/src/main/java/org/variantsync/diffdetective/diff/result/DiffParseException.java
new file mode 100644
index 000000000..c55ff9a61
--- /dev/null
+++ b/src/main/java/org/variantsync/diffdetective/diff/result/DiffParseException.java
@@ -0,0 +1,34 @@
+package org.variantsync.diffdetective.diff.result;
+
+import org.variantsync.diffdetective.diff.DiffLineNumber;
+
+/**
+ * Describes an error that occurred when processing a text-based diff.
+ * @author Benjamin Moosherr
+ */
+public final class DiffParseException extends Exception {
+ private final DiffError error;
+ private final DiffLineNumber lineNumber;
+
+ /**
+ * @param error the error type to be reported
+ * @param lineNumber the source line of the error
+ */
+ public DiffParseException(DiffError error, DiffLineNumber lineNumber) {
+ this.error = error;
+ this.lineNumber = lineNumber;
+ }
+
+ public DiffError getError() {
+ return error;
+ }
+
+ public DiffLineNumber getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "DiffParseException on line " + lineNumber + ": " + error;
+ }
+}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/result/DiffResult.java b/src/main/java/org/variantsync/diffdetective/diff/result/DiffResult.java
deleted file mode 100644
index ce3dfbeb6..000000000
--- a/src/main/java/org/variantsync/diffdetective/diff/result/DiffResult.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.variantsync.diffdetective.diff.result;
-
-import org.tinylog.Logger;
-import org.variantsync.diffdetective.diff.difftree.parse.IllFormedAnnotationException;
-import org.variantsync.functjonal.Result;
-
-import java.util.function.Function;
-
-/**
- * The result of processing a diff.
- * The result either contains the desired result or an error message in case an error occurred.
- * @param The type of the result upon success.
- */
-public class DiffResult {
- private final Result result;
-
- private DiffResult(Result result) {
- this.result = result;
- }
-
- /**
- * Get the internal representation of this result.
- * @see org.variantsync.functjonal.Result
- */
- public Result unwrap() {
- return result;
- }
-
- /**
- * DiffResult is a functor.
- * Applies the given function to this results value if it has one.
- * @param f Function to apply to this result's value.
- * @return A new result with the transformed value.
- * @param Type to which the value of this result is transformed to by the given function.
- */
- public DiffResult map(final Function f) {
- return new DiffResult<>(result.map(f));
- }
-
- /// Constructors
-
- /**
- * Create a result that indicates success.
- * The result will hold the given value and no error message.
- * @param val The result value.
- * @return A success result.
- * @param The type of the return value.
- */
- public static DiffResult Success(T val) {
- return new DiffResult<>(Result.Success(val));
- }
-
- /**
- * Create a result that indicates failure.
- * The result will hold an error message and no value.
- * @param error The error that occurred.
- * @return A failure result.
- * @param The type the result value should have had if no error had been occurred.
- */
- public static DiffResult Failure(DiffError error) {
- return Failure(error, error.id());
- }
-
- /**
- * Convenience constructor to create a result from an {@link IllFormedAnnotationException}.
- * The produced result will indicate failure with the given error message.
- * @param e The exception that was thrown when processing a diff.
- * @return A failure result.
- * @param The type the result value should have had if no error had been occurred.
- */
- public static DiffResult Failure(IllFormedAnnotationException e) {
- return Failure(e.getType(), e.getMessage());
- }
-
-
- /**
- * Create a result that indicates failure.
- * The result will hold the given DiffError and no value.
- * The message will be logged and then discarded.
- * @param error The error that occurred.
- * @param message An additional error message that should be logged.
- * @return A failure result.
- * @param The type the result value should have had if no error had been occurred.
- */
- public static DiffResult Failure(DiffError error, String message) {
- Logger.debug("[DiffResult::Failure] {}", message);
- return new DiffResult<>(Result.Failure(error));
- }
-}
diff --git a/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java
index eba5170ac..b36706db7 100644
--- a/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java
+++ b/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java
@@ -1,5 +1,7 @@
package org.variantsync.diffdetective.feature;
+import java.util.List;
+
import org.prop4j.Literal;
import org.prop4j.Node;
import org.variantsync.diffdetective.diff.difftree.parse.IllFormedAnnotationException;
@@ -52,4 +54,15 @@ public Node parseDiffLine(String line) throws IllFormedAnnotationException {
return formula;
}
+
+ public Node parseDiffLines(List lines) throws IllFormedAnnotationException {
+ var logicalLine = new StringBuilder();
+ for (var it = lines.iterator(); it.hasNext(); ) {
+ String physicalLine = it.next();
+ // Remove the backslash of the line continuation
+ logicalLine.append(physicalLine, 0, physicalLine.length() - (it.hasNext() ? 1 : 0));
+ }
+
+ return parseDiffLine(logicalLine.toString());
+ }
}
diff --git a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java
index 766752706..8e4319721 100644
--- a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java
+++ b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java
@@ -5,12 +5,13 @@
import org.variantsync.diffdetective.datasets.Repository;
import org.variantsync.diffdetective.diff.PatchDiff;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
import org.variantsync.diffdetective.diff.difftree.parse.DiffTreeParser;
import org.variantsync.diffdetective.diff.difftree.render.DiffTreeRenderer;
import org.variantsync.diffdetective.diff.difftree.render.RenderOptions;
import org.variantsync.diffdetective.diff.difftree.serialize.nodeformat.MappingsDiffNodeFormat;
import org.variantsync.diffdetective.diff.difftree.transform.DiffTreeTransformer;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.mining.DiffTreeMiner;
import org.variantsync.diffdetective.mining.RWCompositePatternNodeFormat;
import org.variantsync.diffdetective.mining.RWCompositePatternTreeFormat;
@@ -97,8 +98,8 @@ private static void render(final Path fileToRender) {
Logger.info("Rendering {}", fileToRender);
final DiffTree t;
try {
- t = DiffTree.fromFile(fileToRender, collapseMultipleCodeLines, ignoreEmptyLines, DiffNodeParser.Default).unwrap().getSuccess();
- } catch (IOException e) {
+ t = DiffTree.fromFile(fileToRender, collapseMultipleCodeLines, ignoreEmptyLines, CPPAnnotationParser.Default);
+ } catch (IOException | DiffParseException e) {
Logger.error(e, "Could not read given file '{}'", fileToRender);
return;
}
diff --git a/src/main/java/org/variantsync/diffdetective/mining/DiffTreeMiner.java b/src/main/java/org/variantsync/diffdetective/mining/DiffTreeMiner.java
index 7b87a7935..856533c23 100644
--- a/src/main/java/org/variantsync/diffdetective/mining/DiffTreeMiner.java
+++ b/src/main/java/org/variantsync/diffdetective/mining/DiffTreeMiner.java
@@ -11,7 +11,6 @@
import org.variantsync.diffdetective.datasets.predefined.StanciulescuMarlin;
import org.variantsync.diffdetective.diff.difftree.filter.DiffTreeFilter;
import org.variantsync.diffdetective.diff.difftree.filter.ExplainedFilter;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
import org.variantsync.diffdetective.diff.difftree.serialize.LineGraphExportOptions;
import org.variantsync.diffdetective.diff.difftree.serialize.GraphFormat;
import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.EdgeLabelFormat;
@@ -20,6 +19,7 @@
import org.variantsync.diffdetective.diff.difftree.transform.CutNonEditedSubtrees;
import org.variantsync.diffdetective.diff.difftree.transform.DiffTreeTransformer;
import org.variantsync.diffdetective.diff.difftree.transform.Starfold;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.metadata.ExplainedFilterSummary;
import org.variantsync.diffdetective.mining.formats.DirectedEdgeLabelFormat;
import org.variantsync.diffdetective.mining.formats.MiningNodeFormat;
@@ -44,7 +44,7 @@ public static List Postprocessing(final Repository reposito
final List processing = new ArrayList<>();
processing.add(new CutNonEditedSubtrees());
if (SEARCH_FOR_GOOD_RUNNING_EXAMPLES) {
- processing.add(new RunningExampleFinder(repository == null ? DiffNodeParser.Default : repository.getParseOptions().annotationParser()).
+ processing.add(new RunningExampleFinder(repository == null ? CPPAnnotationParser.Default : repository.getParseOptions().annotationParser()).
The_Diff_Itself_Is_A_Valid_DiffTree_And(
RunningExampleFinder.DefaultExampleConditions,
RunningExampleFinder.DefaultExamplesDirectory.resolve(repository == null ? "unknown" : repository.getRepositoryName())
diff --git a/src/main/java/org/variantsync/diffdetective/mining/RunningExampleFinder.java b/src/main/java/org/variantsync/diffdetective/mining/RunningExampleFinder.java
index 3027ba793..15367bc4d 100644
--- a/src/main/java/org/variantsync/diffdetective/mining/RunningExampleFinder.java
+++ b/src/main/java/org/variantsync/diffdetective/mining/RunningExampleFinder.java
@@ -10,10 +10,10 @@
import org.variantsync.diffdetective.diff.difftree.filter.DiffTreeFilter;
import org.variantsync.diffdetective.diff.difftree.filter.ExplainedFilter;
import org.variantsync.diffdetective.diff.difftree.filter.TaggedPredicate;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
import org.variantsync.diffdetective.diff.difftree.render.DiffTreeRenderer;
import org.variantsync.diffdetective.diff.difftree.transform.ExampleFinder;
-import org.variantsync.diffdetective.diff.result.DiffResult;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.util.Assert;
import java.nio.file.Path;
@@ -33,10 +33,10 @@ public class RunningExampleFinder {
new TaggedPredicate<>("has a complex formula", RunningExampleFinder::hasAtLeastOneComplexFormulaBeforeTheEdit)
);
- private final DiffNodeParser nodeParser;
+ private final CPPAnnotationParser annotationParser;
- public RunningExampleFinder(final DiffNodeParser nodeParser) {
- this.nodeParser = nodeParser;
+ public RunningExampleFinder(final CPPAnnotationParser annotationParser) {
+ this.annotationParser = annotationParser;
}
public ExampleFinder The_Diff_Itself_Is_A_Valid_DiffTree_And(
@@ -45,22 +45,21 @@ public ExampleFinder The_Diff_Itself_Is_A_Valid_DiffTree_And(
{
return new ExampleFinder(
diffTree -> {
- final String localDiff = getDiff(diffTree);
- final DiffResult parseResult = DiffTree.fromDiff(localDiff, true, true, nodeParser);
- // Not every local diff can be parsed to a difftree because diffs are unaware of the underlying language (i.e., CPP).
- // We want only running examples whose diffs describe entire diff trees for easier understanding.
- return parseResult.unwrap().match(
- localTree -> {
- if (treeConditions.test(localTree)) {
- Assert.assertTrue(diffTree.getSource() instanceof GitPatch);
- final GitPatch diffTreeSource = (GitPatch) diffTree.getSource();
- localTree.setSource(diffTreeSource.shallowClone());
- return Optional.of(localTree);
- }
- return Optional.empty();
- },
- error -> Optional.empty()
- );
+ try {
+ final String localDiff = getDiff(diffTree);
+ final DiffTree localTree = DiffTree.fromDiff(localDiff, true, true, annotationParser);
+ // Not every local diff can be parsed to a difftree because diffs are unaware of the underlying language (i.e., CPP).
+ // We want only running examples whose diffs describe entire diff trees for easier understanding.
+ if (treeConditions.test(localTree)) {
+ Assert.assertTrue(diffTree.getSource() instanceof GitPatch);
+ final GitPatch diffTreeSource = (GitPatch) diffTree.getSource();
+ localTree.setSource(diffTreeSource.shallowClone());
+ return Optional.of(localTree);
+ }
+ return Optional.empty();
+ } catch (DiffParseException e) {
+ return Optional.empty();
+ }
},
exportDirectory,
DiffTreeRenderer.WithinDiffDetective()
diff --git a/src/main/resources/tikz_header.tex b/src/main/resources/tikz_header.tex
index 3d021847a..4aba5cbd4 100644
--- a/src/main/resources/tikz_header.tex
+++ b/src/main/resources/tikz_header.tex
@@ -52,7 +52,7 @@
node distance = {9.5mm},
object/.style = {draw, thick},
large/.style = {node distance = 13mm, minimum size=9.5mm},
- textbox/.style = {draw, rectangle, rounded corners=0.4mm, fill=white, inner sep=2pt, font=\tiny},
+ textbox/.style = {draw, rectangle, rounded corners=0.4mm, fill=white, inner sep=2pt, font=\tiny, align=center},
largertextbox/.style = {font=\small},
vtdcloud/.style = {cloud, draw, minimum width=22mm, minimum height=15mm}
}
diff --git a/src/test/java/DiffTreeParserTest.java b/src/test/java/DiffTreeParserTest.java
new file mode 100644
index 000000000..01a9d6803
--- /dev/null
+++ b/src/test/java/DiffTreeParserTest.java
@@ -0,0 +1,97 @@
+import org.variantsync.diffdetective.diff.difftree.DiffTree;
+import org.variantsync.diffdetective.diff.difftree.parse.DiffTreeParser;
+import org.variantsync.diffdetective.diff.difftree.serialize.LineGraphExporter;
+import org.variantsync.diffdetective.diff.difftree.serialize.Format;
+import org.variantsync.diffdetective.diff.difftree.serialize.TikzExporter;
+import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.ChildOrderEdgeFormat;
+import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.DefaultEdgeLabelFormat;
+import org.variantsync.diffdetective.diff.difftree.serialize.nodeformat.FullNodeFormat;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+public class DiffTreeParserTest {
+ private final static Path testDir = Constants.RESOURCE_DIR.resolve("diffs").resolve("parser");
+ private final static String testCaseSuffix = ".diff";
+
+ private static Stream findTestCases(Path dir) throws IOException {
+ return Files
+ .list(dir)
+ .filter(filename -> filename.getFileName().toString().endsWith(testCaseSuffix));
+ }
+
+ public static Stream tests() throws IOException {
+ return findTestCases(testDir);
+ }
+
+ public static Stream wontfixTests() throws IOException {
+ return findTestCases(testDir.resolve("wontfix"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("tests")
+ public void test(Path basename) throws IOException, DiffParseException {
+ testCase(basename);
+ }
+
+ @Disabled("WONTFIX")
+ @ParameterizedTest
+ @MethodSource("wontfixTests")
+ public void wontfixTest(Path testCase) throws IOException, DiffParseException {
+ testCase(testCase);
+ }
+
+ public void testCase(Path testCasePath) throws IOException, DiffParseException {
+ String filename = testCasePath.getFileName().toString();
+ String basename = filename.substring(0, filename.length() - testCaseSuffix.length());
+ var actualPath = testDir.resolve(basename + "_actual.lg");
+ var expectedPath = testDir.resolve(basename + "_expected.lg");
+
+ DiffTree diffTree;
+ try (var inputFile = Files.newBufferedReader(testCasePath)) {
+ diffTree = DiffTreeParser.createDiffTree(
+ inputFile,
+ false,
+ false,
+ CPPAnnotationParser.Default
+ );
+ }
+
+ try (
+ var unbufferedOutput = Files.newOutputStream(actualPath);
+ var output = new BufferedOutputStream(unbufferedOutput)
+ ) {
+ new LineGraphExporter(new Format(new FullNodeFormat(), new ChildOrderEdgeFormat()))
+ .exportDiffTree(diffTree, output);
+ }
+
+ try (
+ var expectedFile = Files.newBufferedReader(expectedPath);
+ var actualFile = Files.newBufferedReader(actualPath);
+ ) {
+ if (!IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) {
+ var visualizationPath = testDir.resolve(basename + ".tex");
+ new TikzExporter(new Format(new FullNodeFormat(), new DefaultEdgeLabelFormat()))
+ .exportFullLatexExample(diffTree, visualizationPath);
+ fail("The DiffTree in file " + testCasePath + " didn't parse correctly. "
+ + "Expected the content of " + expectedPath + " but got the content of " + actualPath + ". "
+ + "Note: A visualisation is available at " + visualizationPath);
+ // Keep output files if the test failed
+ } else {
+ // Delete output files if the test succeeded
+ Files.delete(actualPath);
+ }
+ }
+ }
+}
diff --git a/src/test/java/EditClassesTest.java b/src/test/java/EditClassesTest.java
index 4ec3adc13..992a22468 100644
--- a/src/test/java/EditClassesTest.java
+++ b/src/test/java/EditClassesTest.java
@@ -1,8 +1,10 @@
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import org.variantsync.diffdetective.editclass.proposed.ProposedEditClasses;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import java.io.IOException;
import java.nio.file.Path;
@@ -10,12 +12,12 @@ public class EditClassesTest {
private final static Path testDir = Constants.RESOURCE_DIR.resolve("patterns");
@Test
- public void testAtomics() throws IOException {
+ public void testAtomics() throws IOException, DiffParseException {
final Path path = testDir.resolve("elementary.diff");
- final DiffTree t = DiffTree.fromFile(path, false, true).unwrap().getSuccess();
+ final DiffTree t = DiffTree.fromFile(path, false, true);
t.forAll(node -> {
if (node.isArtifact()) {
- Assert.assertEquals(
+ assertEquals(
node.getLabel(),
ProposedEditClasses.Instance.match(node).getName()
);
diff --git a/src/test/java/ExportTest.java b/src/test/java/ExportTest.java
index 75f4f916b..15cfef7c8 100644
--- a/src/test/java/ExportTest.java
+++ b/src/test/java/ExportTest.java
@@ -1,5 +1,6 @@
-import org.junit.Test;
-import org.tinylog.Logger;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIf;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
import org.variantsync.diffdetective.diff.difftree.serialize.*;
import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.DefaultEdgeLabelFormat;
@@ -8,13 +9,17 @@
import org.variantsync.diffdetective.diff.difftree.serialize.treeformat.CommitDiffDiffTreeLabelFormat;
import org.variantsync.diffdetective.diff.difftree.serialize.Format;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.BufferedOutputStream;
import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ExportTest {
+ private final static Path RESOURCE_DIR = Path.of("src/test/resources/serialize");
+
/**
* Format used for the test export.
*/
@@ -35,26 +40,47 @@ public class ExportTest {
new DefaultEdgeLabelFormat()
);
+ public static boolean isGraphvizInstalled() throws InterruptedException {
+ try {
+ Process dotProcess = new ProcessBuilder("dot", "-V").start();
+ dotProcess.waitFor();
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
@Test
+ @EnabledIf("isGraphvizInstalled")
public void export() throws IOException {
+ var testCasePath = RESOURCE_DIR.resolve("testcase.lg");
+ var actualPath = RESOURCE_DIR.resolve("actual.tex");
+ var expectedPath = RESOURCE_DIR.resolve("expected.tex");
+
// Deserialize the test case.
- var testFile = Path.of("src/test/resources/serialize/testcase.lg");
DiffTree diffTree;
- try (BufferedReader lineGraph = Files.newBufferedReader(testFile)) {
- diffTree = LineGraphImport.fromLineGraph(lineGraph, testFile, importOptions).get(0);
+ try (BufferedReader lineGraph = Files.newBufferedReader(testCasePath)) {
+ diffTree = LineGraphImport.fromLineGraph(lineGraph, testCasePath, importOptions).get(0);
}
// Export the test case
- var tikzOutput = new ByteArrayOutputStream();
+ try (
+ var unbufferedOutput = Files.newOutputStream(actualPath);
+ var output = new BufferedOutputStream(unbufferedOutput)
+ ) {
+ new TikzExporter(format).exportDiffTree(diffTree, output);
+ }
- try {
- new TikzExporter(format).exportDiffTree(diffTree, tikzOutput);
- TestUtils.assertEqualToFile(Path.of("src/test/resources/serialize/expected.tex"), tikzOutput.toString());
- } catch (IOException e) {
- if (e.getMessage().contains("Cannot run program")) {
- Logger.warn("Missing programs! Did you install graphviz? Reason: " + e.getMessage());
+ try (
+ var expectedFile = Files.newBufferedReader(expectedPath);
+ var actualFile = Files.newBufferedReader(actualPath);
+ ) {
+ if (!IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) {
+ fail("The DiffTree in file " + testCasePath + " didn't parse correctly. "
+ + "Expected the content of " + expectedPath + " but got the content of " + actualPath + ". ");
} else {
- throw e;
+ // Keep output file for debugging on errors
+ Files.delete(actualPath);
}
}
}
diff --git a/src/test/java/FeatureIDETest.java b/src/test/java/FeatureIDETest.java
index 719b7b348..1db9dffbf 100644
--- a/src/test/java/FeatureIDETest.java
+++ b/src/test/java/FeatureIDETest.java
@@ -1,9 +1,12 @@
import de.ovgu.featureide.fm.core.editing.NodeCreator;
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.prop4j.*;
import org.variantsync.diffdetective.analysis.logic.SAT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import java.util.HashMap;
import java.util.Map;
@@ -20,12 +23,12 @@ private static Node createFalse() {
@Test
public void trueIsTaut() {
- Assert.assertTrue(SAT.isTautology(createTrue()));
+ assertTrue(SAT.isTautology(createTrue()));
}
@Test
public void falseIsContradiction() {
- Assert.assertFalse(SAT.isSatisfiable(createFalse()));
+ assertFalse(SAT.isSatisfiable(createFalse()));
}
/**
@@ -36,7 +39,7 @@ public void trueAndA_Equals_A() {
final Node tru = createTrue();
final Node a = new Literal("A");
final Node trueAndA = new And(tru, a);
- Assert.assertTrue(SAT.equivalent(trueAndA, a));
+ assertTrue(SAT.equivalent(trueAndA, a));
}
/**
@@ -45,7 +48,7 @@ public void trueAndA_Equals_A() {
@Test
public void A_Equals_A() {
final Node a = new Literal("A");
- Assert.assertTrue(SAT.equivalent(a, a));
+ assertTrue(SAT.equivalent(a, a));
}
@Test
@@ -53,7 +56,7 @@ public void falseOrA_Equals_A() {
final Node no = createFalse();
final Node a = new Literal("A");
final Node noOrA = new Or(no, a);
- Assert.assertTrue(SAT.equivalent(noOrA, a));
+ assertTrue(SAT.equivalent(noOrA, a));
}
// The following three tests failed and where reported in Issue 1111 (https://github.com/FeatureIDE/FeatureIDE/issues/1111).
@@ -69,8 +72,8 @@ public void atomString() {
@Test
public void atomValuesEqual() {
- Assert.assertEquals(createTrue(), new Literal(NodeCreator.varTrue));
- Assert.assertEquals(createFalse(), new Literal(NodeCreator.varFalse));
+ assertEquals(createTrue(), new Literal(NodeCreator.varTrue));
+ assertEquals(createFalse(), new Literal(NodeCreator.varFalse));
}
@Test
diff --git a/src/test/java/FixTrueFalseTest.java b/src/test/java/FixTrueFalseTest.java
index 37ccc771d..a5eb8e451 100644
--- a/src/test/java/FixTrueFalseTest.java
+++ b/src/test/java/FixTrueFalseTest.java
@@ -1,11 +1,11 @@
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.prop4j.*;
import org.variantsync.diffdetective.util.fide.FixTrueFalse;
import java.util.List;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.variantsync.diffdetective.util.fide.FixTrueFalse.False;
import static org.variantsync.diffdetective.util.fide.FixTrueFalse.True;
import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate;
@@ -13,16 +13,13 @@
public class FixTrueFalseTest {
private record TestCase(Node formula, Node expectedResult) {}
- private List testCases;
-
private final static Literal A = new Literal("A");
private final static Literal B = new Literal("B");
private final static Literal C = new Literal("C");
private final static Node SomeIrreducible = new And(A, new Implies(A, B));
- @Before
- public void initTestCases() {
- testCases = List.of(
+ public static List testCases() {
+ return List.of(
new TestCase(new And(True, A), A),
new TestCase(new Or(False, A), A),
new TestCase(new And(False, A), False),
@@ -37,7 +34,7 @@ public void initTestCases() {
new TestCase(new Equals(True, A), A),
new TestCase(new Equals(A, False), negate(A)),
new TestCase(new Equals(False, A), negate(A)),
-
+
new TestCase(
new Equals(
new Or(
@@ -53,10 +50,9 @@ public void initTestCases() {
);
}
- @Test
- public void testAll() {
- for (TestCase testCase : testCases) {
- Assert.assertEquals(FixTrueFalse.EliminateTrueAndFalse(testCase.formula), testCase.expectedResult);
- }
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void test(TestCase testCase) {
+ assertEquals(FixTrueFalse.EliminateTrueAndFalse(testCase.formula), testCase.expectedResult);
}
}
diff --git a/src/test/java/LineGraphTest.java b/src/test/java/LineGraphTest.java
index 6ee2d7cbb..30d681b58 100644
--- a/src/test/java/LineGraphTest.java
+++ b/src/test/java/LineGraphTest.java
@@ -1,8 +1,6 @@
import org.apache.commons.io.IOUtils;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.tinylog.Logger;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
import org.variantsync.diffdetective.diff.difftree.serialize.*;
import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.DefaultEdgeLabelFormat;
@@ -10,12 +8,15 @@
import org.variantsync.diffdetective.diff.difftree.serialize.treeformat.CommitDiffDiffTreeLabelFormat;
import org.variantsync.diffdetective.util.IO;
+import static org.junit.jupiter.api.Assertions.fail;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.stream.Stream;
/**
* For testing the import of a line graph.
@@ -31,45 +32,40 @@ public class LineGraphTest {
IMPORT_OPTIONS
);
- private static List TEST_FILES;
-
- @BeforeClass
- public static void init() throws IOException {
- TEST_FILES = Files.list(Paths.get("src/test/resources/line_graph")).toList();
+ public static Stream testCases() throws IOException {
+ return Files.list(Paths.get("src/test/resources/line_graph"));
}
- /**
- * Test the import of a line graph.
- */
- @Test
- public void idempotentReadWrite() throws IOException {
- for (final Path testFile : TEST_FILES) {
- Logger.info("Testing {}", testFile);
- List diffTrees;
- try (BufferedReader lineGraph = Files.newBufferedReader(testFile)) {
- diffTrees = LineGraphImport.fromLineGraph(lineGraph, testFile, IMPORT_OPTIONS);
- }
- assertConsistencyForAll(diffTrees);
+ /**
+ * Test the import of a line graph.
+ */
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void idempotentReadWrite(Path testFile) throws IOException {
+ List diffTrees;
+ try (BufferedReader lineGraph = Files.newBufferedReader(testFile)) {
+ diffTrees = LineGraphImport.fromLineGraph(lineGraph, testFile, IMPORT_OPTIONS);
+ }
+ assertConsistencyForAll(diffTrees);
- Path actualPath = testFile.getParent().resolve(testFile.getFileName().toString() + ".actual");
- try (var output = IO.newBufferedOutputStream(actualPath)) {
- LineGraphExport.toLineGraphFormat(diffTrees, EXPORT_OPTIONS, output);
- }
+ Path actualPath = testFile.getParent().resolve(testFile.getFileName().toString() + ".actual");
+ try (var output = IO.newBufferedOutputStream(actualPath)) {
+ LineGraphExport.toLineGraphFormat(diffTrees, EXPORT_OPTIONS, output);
+ }
- try (
- var expectedFile = Files.newBufferedReader(testFile);
- var actualFile = Files.newBufferedReader(actualPath);
- ) {
- if (!IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) {
- Assert.fail("The file " + testFile + " couldn't be exported or imported without modifications");
- } else {
- // Only keep output file on errors
- Files.delete(actualPath);
- }
+ try (
+ var expectedFile = Files.newBufferedReader(testFile);
+ var actualFile = Files.newBufferedReader(actualPath);
+ ) {
+ if (!IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) {
+ fail("The file " + testFile + " couldn't be exported or imported without modifications");
+ } else {
+ // Only keep output file on errors
+ Files.delete(actualPath);
}
}
- }
-
+ }
+
/**
* Check consistency of {@link DiffTree DiffTrees}.
*
diff --git a/src/test/java/LinuxParsingTest.java b/src/test/java/LinuxParsingTest.java
index 9bad73fe7..90303f651 100644
--- a/src/test/java/LinuxParsingTest.java
+++ b/src/test/java/LinuxParsingTest.java
@@ -1,8 +1,7 @@
import org.variantsync.diffdetective.diff.difftree.DiffTree;
import org.variantsync.diffdetective.diff.difftree.render.DiffTreeRenderer;
-import org.variantsync.diffdetective.diff.result.DiffError;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import org.variantsync.diffdetective.util.Assert;
-import org.variantsync.functjonal.Result;
import java.io.IOException;
import java.nio.file.Path;
@@ -12,17 +11,11 @@ public class LinuxParsingTest {
private final static Path testDir = Constants.RESOURCE_DIR.resolve("linux");
// @Test
- public void test1() throws IOException {
+ public void test1() throws IOException, DiffParseException {
final String testFilename = "test1.diff";
final Path path = testDir.resolve(testFilename);
- final Result parseResult =
- DiffTree.fromFile(path, false, true).unwrap();
+ final DiffTree t = DiffTree.fromFile(path, false, true);
- if (parseResult.isFailure()) {
- throw new AssertionError("Could not parse " + path + " because " + parseResult.getFailure());
- }
-
- final DiffTree t = parseResult.getSuccess();
// new FeatureExpressionFilter(LinuxKernel::isFeature).transform(t);
t.forAll(n -> {
diff --git a/src/test/java/MarlinDebug.java b/src/test/java/MarlinDebug.java
index 4936e4619..9357e7f78 100644
--- a/src/test/java/MarlinDebug.java
+++ b/src/test/java/MarlinDebug.java
@@ -2,9 +2,9 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
import org.prop4j.Node;
import org.tinylog.Logger;
import org.variantsync.diffdetective.datasets.DatasetDescription;
@@ -24,12 +24,15 @@
import org.variantsync.diffdetective.util.Clock;
import org.variantsync.diffdetective.validation.Validation;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Deprecated
+@Disabled
public class MarlinDebug {
private record RepoInspection(
List suspiciousCommits,
@@ -42,7 +45,7 @@ private record RepoInspection(
private static RepoInspection MARLIN, PHP;
- @Before
+ @BeforeEach
public void init() {
{
DatasetDescription marlin = new DatasetDescription(
@@ -126,7 +129,7 @@ public static void testCommit(final RepoInspection repoInspection, final String
public static void asMiningTask(final RepoInspection repoInspection, final String commitHash) throws Exception {
final Git git = repoInspection.repo.getGitRepo().run();
- Assert.assertNotNull(git);
+ assertNotNull(git);
final RevWalk revWalk = new RevWalk(git.getRepository());
final RevCommit childCommit = revWalk.parseCommit(ObjectId.fromString(commitHash));
@@ -140,7 +143,7 @@ public static void asMiningTask(final RepoInspection repoInspection, final Strin
public static void asValidationTask(final RepoInspection repoInspection, final String commitHash) throws Exception {
final Git git = repoInspection.repo.getGitRepo().run();
- Assert.assertNotNull(git);
+ assertNotNull(git);
final RevWalk revWalk = new RevWalk(git.getRepository());
final RevCommit childCommit = revWalk.parseCommit(ObjectId.fromString(commitHash));
diff --git a/src/test/java/MoveDetectionTest.java b/src/test/java/MoveDetectionTest.java
index 2ca98a82d..cd89c95ef 100644
--- a/src/test/java/MoveDetectionTest.java
+++ b/src/test/java/MoveDetectionTest.java
@@ -1,6 +1,7 @@
import org.variantsync.diffdetective.diff.difftree.DiffTree;
import org.variantsync.diffdetective.diff.difftree.render.DiffTreeRenderer;
import org.variantsync.diffdetective.diff.difftree.transform.NaiveMovedArtifactDetection;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import java.io.IOException;
import java.nio.file.Path;
@@ -10,8 +11,8 @@ public class MoveDetectionTest {
private static final Path genDir = resDir.resolve("gen");
// @Test
- public void simpleTest() throws IOException {
- final DiffTree t = DiffTree.fromFile(resDir.resolve("simple.txt"), true, true).unwrap().getSuccess();
+ public void simpleTest() throws IOException, DiffParseException {
+ final DiffTree t = DiffTree.fromFile(resDir.resolve("simple.txt"), true, true);
final DiffTreeRenderer renderer = DiffTreeRenderer.WithinDiffDetective();
renderer.render(t, "MoveDetectionTestSimpleTest_Before", genDir);
new NaiveMovedArtifactDetection().transform(t);
diff --git a/src/test/java/PCTest.java b/src/test/java/PCTest.java
index f19f33231..cb5c3d0b7 100644
--- a/src/test/java/PCTest.java
+++ b/src/test/java/PCTest.java
@@ -1,16 +1,19 @@
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.prop4j.And;
import org.prop4j.Literal;
import org.prop4j.Node;
import org.tinylog.Logger;
import org.variantsync.diffdetective.analysis.logic.SAT;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.List;
import java.util.Map;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate;
public class PCTest {
@@ -20,7 +23,12 @@ public class PCTest {
private static final Literal D = new Literal("D");
private static final Literal E = new Literal("E");
record ExpectedPC(Node before, Node after) {}
- record TestCase(Path file, Map expectedResult) {}
+ record TestCase(Path file, Map expectedResult) {
+ @Override
+ public String toString() {
+ return file.toString();
+ }
+ }
private final static Path testDir = Constants.RESOURCE_DIR.resolve("pctest");
private final static TestCase a = new TestCase(
@@ -53,41 +61,32 @@ private static String errorAt(final String node, String time, Node is, Node shou
return time + " PC of node \"" + node + "\" is \"" + is + "\" but expected \"" + should + "\"!";
}
- private void test(final TestCase testCase) throws IOException {
+ public static List testCases() {
+ return List.of(a, elif, elze);
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void test(final TestCase testCase) throws IOException, DiffParseException {
final Path path = testDir.resolve(testCase.file);
- final DiffTree t = DiffTree.fromFile(path, false, true).unwrap().getSuccess();
+ final DiffTree t = DiffTree.fromFile(path, false, true);
t.forAll(node -> {
if (node.isArtifact()) {
final String text = node.getLabel().trim();
final ExpectedPC expectedPC = testCase.expectedResult.getOrDefault(text, null);
if (expectedPC != null) {
Node pc = node.getBeforePresenceCondition();
- Assert.assertTrue(
- errorAt(text, "before", pc, expectedPC.before),
- SAT.equivalent(pc, expectedPC.before));
+ assertTrue(
+ SAT.equivalent(pc, expectedPC.before),
+ errorAt(text, "before", pc, expectedPC.before));
pc = node.getAfterPresenceCondition();
- Assert.assertTrue(
- errorAt(text, "after", pc, expectedPC.after),
- SAT.equivalent(pc, expectedPC.after));
+ assertTrue(
+ SAT.equivalent(pc, expectedPC.after),
+ errorAt(text, "after", pc, expectedPC.after));
} else {
Logger.warn("No expected PC specified for node '{}'!", text);
}
}
});
}
-
- @Test
- public void testA() throws IOException {
- test(a);
- }
-
- @Test
- public void testElif() throws IOException {
- test(elif);
- }
-
- @Test
- public void testElse() throws IOException {
- test(elze);
- }
}
diff --git a/src/test/java/PrintWorkingTreeDiff.java b/src/test/java/PrintWorkingTreeDiff.java
index 24d5b2334..5c1565b84 100644
--- a/src/test/java/PrintWorkingTreeDiff.java
+++ b/src/test/java/PrintWorkingTreeDiff.java
@@ -1,15 +1,14 @@
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
import org.variantsync.diffdetective.datasets.ParseOptions.DiffStoragePolicy;
import org.variantsync.diffdetective.datasets.Repository;
import org.variantsync.diffdetective.diff.CommitDiff;
import org.variantsync.diffdetective.diff.GitDiffer;
import org.variantsync.diffdetective.diff.PatchDiff;
import org.variantsync.diffdetective.diff.result.CommitDiffResult;
-import org.variantsync.diffdetective.util.IO;
import java.io.IOException;
import java.nio.file.Path;
@@ -18,6 +17,7 @@
/**
* Perform "git diff" on a git repository.
*/
+@Disabled
public class PrintWorkingTreeDiff {
@Test
diff --git a/src/test/java/StarfoldTest.java b/src/test/java/StarfoldTest.java
index 855b43583..c4b8f1831 100644
--- a/src/test/java/StarfoldTest.java
+++ b/src/test/java/StarfoldTest.java
@@ -1,51 +1,49 @@
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
import org.variantsync.diffdetective.diff.difftree.transform.Starfold;
-import org.variantsync.diffdetective.util.FileUtils;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
-import java.util.function.Function;
import java.util.stream.Stream;
public class StarfoldTest {
- private static final Path RESOURCE_DIR = Constants.RESOURCE_DIR.resolve("starfold");
+ private static final Path INPUT_DIR = Constants.RESOURCE_DIR.resolve("starfold");
+ private static final Path EXPECTED_DIR = INPUT_DIR.resolve("expected");
- private record TestCase(Path inputDiff, Path expectedDiffRespectingNodeOrder, Path expectedDiffIgnoringNodeOrder) {
- TestCase(final String filename) {
- this(
- RESOURCE_DIR.resolve(filename),
- RESOURCE_DIR.resolve("expected").resolve("respectNodeOrder").resolve(filename),
- RESOURCE_DIR.resolve("expected").resolve("ignoreNodeOrder").resolve(filename)
- );
- }
+ public static Stream testCases() {
+ return Stream
+ .of("2x2", "nesting1")
+ .map(s -> s + ".diff");
}
- private static final List TEST_CASES = Stream.of(
- "2x2", "nesting1"
- ).map(s -> s + ".diff").map(TestCase::new).toList();
-
- private void test(final Starfold starfold, Function getExpectedResultFile) throws IOException {
- for (TestCase testCase : TEST_CASES) {
- final DiffTree t = DiffTree.fromFile(testCase.inputDiff, true, true).unwrap().getSuccess();
- starfold.transform(t);
- TestUtils.assertEqualToFile(
- getExpectedResultFile.apply(testCase),
- t.toTextDiff().trim()
- );
- }
+ private void test(Path inputDiff, final Starfold starfold, Path expected) throws IOException, DiffParseException {
+ final DiffTree t = DiffTree.fromFile(inputDiff, true, true);
+ starfold.transform(t);
+ TestUtils.assertEqualToFile(
+ expected,
+ t.toTextDiff().trim()
+ );
}
- @Test
- public void testRespectNodeOrder() throws IOException {
- test(Starfold.RespectNodeOrder(), TestCase::expectedDiffRespectingNodeOrder);
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void testRespectNodeOrder(String filename) throws IOException, DiffParseException {
+ test(
+ INPUT_DIR.resolve(filename),
+ Starfold.RespectNodeOrder(),
+ EXPECTED_DIR.resolve("respectNodeOrder").resolve(filename)
+ );
}
- @Test
- public void testIgnoreNodeOrder() throws IOException {
- test(Starfold.IgnoreNodeOrder(), TestCase::expectedDiffIgnoringNodeOrder);
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void testIgnoreNodeOrder(String filename) throws IOException, DiffParseException {
+ test(
+ INPUT_DIR.resolve(filename),
+ Starfold.IgnoreNodeOrder(),
+ EXPECTED_DIR.resolve("ignoreNodeOrder").resolve(filename)
+ );
}
}
diff --git a/src/test/java/TestLineNumbers.java b/src/test/java/TestLineNumbers.java
index e692cd86e..7bcb95552 100644
--- a/src/test/java/TestLineNumbers.java
+++ b/src/test/java/TestLineNumbers.java
@@ -1,10 +1,12 @@
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.variantsync.diffdetective.diff.DiffLineNumber;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import org.variantsync.functjonal.Pair;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
@@ -15,10 +17,8 @@
public class TestLineNumbers {
private static final Path resDir = Constants.RESOURCE_DIR.resolve("diffs/linenumbers");
private record TestCase(String filename, Map> expectedLineNumbers) { }
- private List testCases;
- @Before
- public void initTestCases() {
+ public static List testCases() {
// Testcases rely on stability of IDs
final var elifchain_map = new HashMap>();
@@ -56,11 +56,11 @@ public void initTestCases() {
deleteMLM_map.put(267, new Pair<>(new DiffLineNumber(3, 3, -1), new DiffLineNumber(4, 4, -1)));
TestCase deleteMLM = new TestCase("deleteMLM.txt", deleteMLM_map);
- testCases = List.of(elifchain, lineno1, deleteMLM);
+ return List.of(elifchain, lineno1, deleteMLM);
}
- private static DiffTree loadFullDiff(final Path p) throws IOException {
- return DiffTree.fromFile(p, false, false).unwrap().getSuccess();
+ private static DiffTree loadFullDiff(final Path p) throws IOException, DiffParseException {
+ return DiffTree.fromFile(p, false, false);
}
private static void printLineNumbers(final DiffTree diffTree) {
@@ -76,10 +76,10 @@ private static void printLineNumbers(final DiffTree diffTree) {
System.out.println();
}
- private static String generateTestCaseCode(final Path p) throws IOException {
+ private static String generateTestCaseCode(final Path p) throws IOException, DiffParseException {
final DiffTree diffTree = loadFullDiff(p);
final Function toConstructorCall = l ->
- "new DiffLineNumber(" + l.inDiff + ", " + l.beforeEdit + ", " + l.afterEdit + ")";
+ "new DiffLineNumber(" + l.inDiff() + ", " + l.beforeEdit() + ", " + l.afterEdit() + ")";
String testName = p.getFileName().toString();
testName = testName.substring(0, testName.lastIndexOf("."));
String mapName = testName + "_map";
@@ -99,10 +99,10 @@ private static String generateTestCaseCode(final Path p) throws IOException {
}
// @Test
- public void generateTestCode() throws IOException {
+ public void generateTestCode() throws IOException, DiffParseException {
final StringBuilder listof = new StringBuilder("List.of(");
boolean first = true;
- for (final TestCase s : testCases) {
+ for (final TestCase s : testCases()) {
if (first) {
first = false;
} else {
@@ -114,25 +114,23 @@ public void generateTestCode() throws IOException {
System.out.println("testCases = " + listof);
}
-// @Test
- public void printLineNumbers() throws IOException {
- for (final TestCase s : testCases) {
- System.out.println("Diff of " + s.filename());
- printLineNumbers(loadFullDiff(resDir.resolve(s.filename())));
- }
+// @ParameterizedTest
+ @MethodSource("testCases")
+ public void printLineNumbers(TestCase testCase) throws IOException, DiffParseException {
+ System.out.println("Diff of " + testCase.filename());
+ printLineNumbers(loadFullDiff(resDir.resolve(testCase.filename())));
}
- @Test
- public void testLineNumbers() throws IOException {
- for (final TestCase s : testCases) {
- final DiffTree t = loadFullDiff(resDir.resolve(s.filename()));
- t.forAll(node -> {
- var fromTo = s.expectedLineNumbers.get(node.getID());
- final DiffLineNumber from = fromTo.first();
- final DiffLineNumber to = fromTo.second();
- Assert.assertEquals(from, node.getFromLine());
- Assert.assertEquals(to, node.getToLine());
- });
- }
+ @ParameterizedTest
+ @MethodSource("testCases")
+ public void testLineNumbers(TestCase testCase) throws IOException, DiffParseException {
+ final DiffTree t = loadFullDiff(resDir.resolve(testCase.filename()));
+ t.forAll(node -> {
+ var fromTo = testCase.expectedLineNumbers.get(node.getID());
+ final DiffLineNumber from = fromTo.first();
+ final DiffLineNumber to = fromTo.second();
+ assertEquals(from, node.getFromLine());
+ assertEquals(to, node.getToLine());
+ });
}
}
diff --git a/src/test/java/TestMultiLineMacros.java b/src/test/java/TestMultiLineMacros.java
index b8941829a..70b90000d 100644
--- a/src/test/java/TestMultiLineMacros.java
+++ b/src/test/java/TestMultiLineMacros.java
@@ -1,8 +1,8 @@
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.tinylog.Logger;
import org.variantsync.diffdetective.diff.difftree.DiffTree;
-import org.variantsync.diffdetective.diff.difftree.parse.DiffNodeParser;
+import org.variantsync.diffdetective.feature.CPPAnnotationParser;
import org.variantsync.diffdetective.diff.difftree.parse.DiffTreeParser;
import org.variantsync.diffdetective.diff.difftree.serialize.LineGraphExportOptions;
import org.variantsync.diffdetective.diff.difftree.serialize.DiffTreeSerializeDebugData;
@@ -11,9 +11,12 @@
import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.DefaultEdgeLabelFormat;
import org.variantsync.diffdetective.diff.difftree.serialize.nodeformat.DebugDiffNodeFormat;
import org.variantsync.diffdetective.diff.difftree.serialize.treeformat.CommitDiffDiffTreeLabelFormat;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import org.variantsync.diffdetective.util.IO;
import org.variantsync.diffdetective.util.StringUtils;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
@@ -22,22 +25,21 @@
public class TestMultiLineMacros {
private static final Path resDir = Constants.RESOURCE_DIR.resolve("multilinemacros");
- public void diffToDiffTree(LineGraphExportOptions exportOptions, Path p) throws IOException {
+ public void diffToDiffTree(LineGraphExportOptions exportOptions, Path p) throws IOException, DiffParseException {
DiffTree tree;
try (BufferedReader fullDiff = Files.newBufferedReader(p)) {
tree = DiffTreeParser.createDiffTree(
fullDiff,
true,
- true,
- DiffNodeParser.Default).unwrap().getSuccess();
+ false,
+ CPPAnnotationParser.Default);
}
try (var destination = IO.newBufferedOutputStream(resDir.resolve("gen").resolve(p.getFileName() + ".lg"))) {
destination.write(("t # 1" + StringUtils.LINEBREAK).getBytes());
-
final DiffTreeSerializeDebugData debugData = LineGraphExport.toLineGraphFormat(tree, exportOptions, destination);
- Assert.assertNotNull(debugData);
+ assertNotNull(debugData);
Logger.info("Parsed {} nodes of diff type NON.", debugData.numExportedNonNodes);
Logger.info("Parsed {} nodes of diff type ADD.", debugData.numExportedAddNodes);
Logger.info("Parsed {} nodes of diff type REM.", debugData.numExportedRemNodes);
@@ -45,8 +47,9 @@ public void diffToDiffTree(LineGraphExportOptions exportOptions, Path p) throws
}
}
- @Test
- public void test() throws IOException {
+ @ParameterizedTest
+ @ValueSource(strings = { "mldiff1.txt", "diffWithComments.txt" })
+ public void test(String filename) throws IOException, DiffParseException {
final LineGraphExportOptions exportOptions = new LineGraphExportOptions(
GraphFormat.DIFFTREE,
new CommitDiffDiffTreeLabelFormat(),
@@ -54,7 +57,6 @@ public void test() throws IOException {
new DefaultEdgeLabelFormat()
);
- diffToDiffTree(exportOptions, resDir.resolve("mldiff1.txt"));
- diffToDiffTree(exportOptions, resDir.resolve("diffWithComments.txt"));
+ diffToDiffTree(exportOptions, resDir.resolve(filename));
}
}
diff --git a/src/test/java/TestUtils.java b/src/test/java/TestUtils.java
index f4565f8bd..6f6593ed6 100644
--- a/src/test/java/TestUtils.java
+++ b/src/test/java/TestUtils.java
@@ -1,12 +1,13 @@
import org.apache.commons.io.IOUtils;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
-import static org.junit.Assert.assertTrue;
-
public final class TestUtils {
/**
* Compare two line graphs.
@@ -16,8 +17,9 @@ public final class TestUtils {
*/
public static void assertEqualToFile(final Path filePath, final String actual) throws IOException {
try (BufferedReader expected = Files.newBufferedReader(filePath)) {
- assertTrue("expected content of " + filePath + " but was:<" + actual + ">",
- IOUtils.contentEqualsIgnoreEOL(expected, new StringReader(actual)));
+ assertTrue(
+ IOUtils.contentEqualsIgnoreEOL(expected, new StringReader(actual)),
+ "expected content of " + filePath + " but was:<" + actual + ">");
}
}
}
diff --git a/src/test/java/TreeTransformersTest.java b/src/test/java/TreeTransformersTest.java
index da1c7cb8f..6004e3abe 100644
--- a/src/test/java/TreeTransformersTest.java
+++ b/src/test/java/TreeTransformersTest.java
@@ -1,6 +1,6 @@
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
import org.variantsync.diffdetective.datasets.Repository;
import org.variantsync.diffdetective.datasets.predefined.StanciulescuMarlin;
import org.variantsync.diffdetective.diff.PatchDiff;
@@ -14,13 +14,17 @@
import org.variantsync.diffdetective.diff.difftree.serialize.nodeformat.TypeDiffNodeFormat;
import org.variantsync.diffdetective.diff.difftree.serialize.treeformat.CommitDiffDiffTreeLabelFormat;
import org.variantsync.diffdetective.diff.difftree.transform.DiffTreeTransformer;
+import org.variantsync.diffdetective.diff.result.DiffParseException;
import org.variantsync.diffdetective.mining.DiffTreeMiner;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
+@Disabled
public class TreeTransformersTest {
private static final boolean RENDER = true;
private static final Path resDir = Constants.RESOURCE_DIR.resolve("diffs/collapse");
@@ -42,8 +46,8 @@ public class TreeTransformersTest {
private static final Consumer INFO = System.out::println;
- private void transformAndRender(String diffFileName) throws IOException {
- final DiffTree t = DiffTree.fromFile(resDir.resolve(diffFileName), true, true).unwrap().getSuccess();
+ private void transformAndRender(String diffFileName) throws IOException, DiffParseException {
+ final DiffTree t = DiffTree.fromFile(resDir.resolve(diffFileName), true, true);
transformAndRender(t, diffFileName, "0", null);
}
@@ -78,63 +82,55 @@ private void transformAndRender(DiffTree diffTree, String name, String commit, R
}
}
- @Before
+ @BeforeEach
public void init() {
// Main.setupLogger(Level.INFO);
// DiffTreeTransformer.checkDependencies(transformers);
}
-// @Test
- @Ignore
- public void simpleTest() throws IOException {
+ @Test
+ public void simpleTest() throws IOException, DiffParseException {
transformAndRender("simple.txt");
}
-// @Test
- @Ignore
- public void elifTest() throws IOException {
+ @Test
+ public void elifTest() throws IOException, DiffParseException {
transformAndRender("elif.txt");
}
private void testCommit(String file, String commitHash) throws IOException {
final Repository marlin = StanciulescuMarlin.fromZipInDiffDetectiveAt(Path.of("."));
final PatchDiff patch = DiffTreeParser.parsePatch(marlin, file, commitHash);
- Assert.assertNotNull(patch);
+ assertNotNull(patch);
transformAndRender(patch.getDiffTree(), file, commitHash, marlin);
}
-// @Test
- @Ignore
+ @Test
public void testWurmcoil() throws IOException {
testCommit("Marlin/pins.h", "d6d6fb8930be8d0b3bd34592c915732937c6f4d9");
}
-// @Test
- @Ignore
+ @Test
public void testConfiguration_adv() throws IOException {
testCommit("Marlin/example_configurations/RigidBot/Configuration_adv.h", "d3fe3a0962fdbdcd9548abaf765e0cff72d9cf8d");
}
-// @Test
- @Ignore
+ @Test
public void test_pins_SANGUINOLOLU_11() throws IOException {
testCommit("Marlin/pins_SANGUINOLOLU_11.h", "d3fe3a0962fdbdcd9548abaf765e0cff72d9cf8d");
}
-// @Test
- @Ignore
+ @Test
public void test_pins_RAMPS_13() throws IOException {
testCommit("Marlin/pins_RAMPS_13.h", "d882e1aee7fb4e4afb43445899b477caf1fffce3");
}
-// @Test
- @Ignore
+ @Test
public void test_SanityCheck() throws IOException {
testCommit("Marlin/SanityCheck.h", "cbd582865e2a76b7be3b03533a0e06e8daf76f15");
}
-// @Test
- @Ignore
+ @Test
public void test_pins_MINIRAMBO() throws IOException {
testCommit("Marlin/pins_MINIRAMBO.h", "50f1a8fd92b351bf1fa29e5cd31f24fc884999c0");
}
diff --git a/src/test/java/TseytinTest.java b/src/test/java/TseytinTest.java
index 26cf9e011..71775ca29 100644
--- a/src/test/java/TseytinTest.java
+++ b/src/test/java/TseytinTest.java
@@ -1,6 +1,5 @@
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.prop4j.*;
import org.variantsync.diffdetective.analysis.logic.SAT;
import org.variantsync.diffdetective.analysis.logic.Tseytin;
@@ -9,34 +8,33 @@
import java.util.ArrayList;
import java.util.List;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate;
public class TseytinTest {
private static final Literal A = new Literal("A");
private static final Literal B = new Literal("B");
- private static final Literal C = new Literal("C");
- private static final Literal D = new Literal("D");
- private static List tautologyTestCases;
- private static List contradictoryTestCases;
- private static List satisfiableTestCases;
-
- @BeforeClass
- public static void setupTestCases() {
- tautologyTestCases = List.of(
+ public static List tautologyTestCases() {
+ return List.of(
FixTrueFalse.True,
negate(FixTrueFalse.False),
new Or(A, negate(A)),
new Implies(new And(A, new Implies(A, B)), B) // modus ponens
);
+ }
- contradictoryTestCases = List.of(
+ public static List contradictoryTestCases() {
+ return List.of(
FixTrueFalse.False,
negate(FixTrueFalse.True),
new And(A, negate(A))
);
+ }
- satisfiableTestCases = new ArrayList<>(List.of(
+ public static List satisfiableTestCases() {
+ var satisfiableTestCases = new ArrayList<>(List.of(
A,
negate(A),
negate(new And(A, B)),
@@ -45,37 +43,35 @@ public static void setupTestCases() {
new Implies(A, B),
new Equals(A, B)
));
- satisfiableTestCases.addAll(tautologyTestCases);
+ satisfiableTestCases.addAll(tautologyTestCases());
+ return satisfiableTestCases;
}
- @Test
- public void testSAT() {
- for (final Node formula : satisfiableTestCases) {
- Assert.assertTrue(formula.toString(), SAT.isSatisfiableNoTseytin(formula));
- Assert.assertTrue(formula.toString(), SAT.isSatisfiableAlwaysTseytin(formula));
- }
+ @ParameterizedTest
+ @MethodSource("satisfiableTestCases")
+ public void testSAT(Node formula) {
+ assertTrue(SAT.isSatisfiableNoTseytin(formula), formula.toString());
+ assertTrue(SAT.isSatisfiableAlwaysTseytin(formula), formula.toString());
}
- @Test
- public void testTAUT() {
- for (final Node formula : tautologyTestCases) {
- final Node no = negate(formula);
- Assert.assertFalse(
- no.toString(),
- SAT.isSatisfiableNoTseytin(no)
- );
- Assert.assertFalse(
- "Expected SAT(tseytin(" + no + ")) = SAT(" + Tseytin.toEquisatisfiableCNF(no) + ") = false but got true.",
- SAT.isSatisfiableAlwaysTseytin(no)
- );
- }
+ @ParameterizedTest
+ @MethodSource("tautologyTestCases")
+ public void testTAUT(Node formula) {
+ final Node no = negate(formula);
+ assertFalse(
+ SAT.isSatisfiableNoTseytin(no),
+ no.toString()
+ );
+ assertFalse(
+ SAT.isSatisfiableAlwaysTseytin(no),
+ "Expected SAT(tseytin(" + no + ")) = SAT(" + Tseytin.toEquisatisfiableCNF(no) + ") = false but got true."
+ );
}
- @Test
- public void testContradictions() {
- for (final Node formula : contradictoryTestCases) {
- Assert.assertFalse(formula.toString(), SAT.isSatisfiableNoTseytin(formula));
- Assert.assertFalse(formula.toString(), SAT.isSatisfiableAlwaysTseytin(formula));
- }
+ @ParameterizedTest
+ @MethodSource("contradictoryTestCases")
+ public void testContradictions(Node formula) {
+ assertFalse(SAT.isSatisfiableNoTseytin(formula), formula.toString());
+ assertFalse(SAT.isSatisfiableAlwaysTseytin(formula), formula.toString());
}
}
diff --git a/src/test/resources/diffs/parser/.gitignore b/src/test/resources/diffs/parser/.gitignore
new file mode 100644
index 000000000..e79a2a109
--- /dev/null
+++ b/src/test/resources/diffs/parser/.gitignore
@@ -0,0 +1,10 @@
+# People might run LaTeX to visualize failed tests and there are many LaTeX
+# files which will be generated, so just ignore all files, except explicitly
+# allowed.
+*
+
+!.gitignore
+!*.diff
+!*_expected.lg
+!/wontfix/
+!README.md
diff --git a/src/test/resources/diffs/parser/01.diff b/src/test/resources/diffs/parser/01.diff
new file mode 100644
index 000000000..2030b9966
--- /dev/null
+++ b/src/test/resources/diffs/parser/01.diff
@@ -0,0 +1,3 @@
+-#ifdef \
+ A
+-#endif
diff --git a/src/test/resources/diffs/parser/01_expected.lg b/src/test/resources/diffs/parser/01_expected.lg
new file mode 100644
index 000000000..d1a507823
--- /dev/null
+++ b/src/test/resources/diffs/parser/01_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 3, diff: 3, new:-1);A;#ifdef \; A
+v 195 ADD;ARTIFACT;(old: -1, diff: 2, new:1);(old: -1, diff: 3, new:2);; A
+e 136 16 b;0
+e 195 16 a;1
diff --git a/src/test/resources/diffs/parser/02.diff b/src/test/resources/diffs/parser/02.diff
new file mode 100644
index 000000000..bc41b0595
--- /dev/null
+++ b/src/test/resources/diffs/parser/02.diff
@@ -0,0 +1,4 @@
+-#if A
+ Code
+-#endif \
+ // Hehe
diff --git a/src/test/resources/diffs/parser/02_expected.lg b/src/test/resources/diffs/parser/02_expected.lg
new file mode 100644
index 000000000..9293d49db
--- /dev/null
+++ b/src/test/resources/diffs/parser/02_expected.lg
@@ -0,0 +1,8 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 3, diff: 3, new:-1);A;#if A
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:1);(old: 3, diff: 3, new:2);; Code
+v 323 ADD;ARTIFACT;(old: -1, diff: 4, new:2);(old: -1, diff: 5, new:3);; // Hehe
+e 136 16 b;0
+e 211 136 b;0
+e 211 16 a;1
+e 323 16 a;2
diff --git a/src/test/resources/diffs/parser/03.diff b/src/test/resources/diffs/parser/03.diff
new file mode 100644
index 000000000..06d38aa66
--- /dev/null
+++ b/src/test/resources/diffs/parser/03.diff
@@ -0,0 +1,11 @@
+ #if A \
+- && B \
+- && C \
++ || B \
+- && D
++ || C \
+ // Foo
+ Code
+-#endif \
++#endif
+ // Bar
diff --git a/src/test/resources/diffs/parser/03_expected.lg b/src/test/resources/diffs/parser/03_expected.lg
new file mode 100644
index 000000000..362f8320d
--- /dev/null
+++ b/src/test/resources/diffs/parser/03_expected.lg
@@ -0,0 +1,12 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 7, diff: 9, new:-1);A & B & C & D;#if A \; && B \; && C \; && D
+v 523 REM;ARTIFACT;(old: 5, diff: 7, new:-1);(old: 6, diff: 8, new:-1);; // Foo
+v 595 NON;ARTIFACT;(old: 6, diff: 8, new:5);(old: 7, diff: 9, new:6);;Code
+v 128 ADD;IF;(old: -1, diff: 1, new:1);(old: -1, diff: 10, new:6);A | B | C;#if A \; || B \; || C \; // Foo
+v 771 ADD;ARTIFACT;(old: -1, diff: 11, new:7);(old: -1, diff: 12, new:8);; // Bar
+e 136 16 b;0
+e 523 136 b;0
+e 595 136 b;1
+e 595 128 a;0
+e 128 16 a;1
+e 771 16 a;2
diff --git a/src/test/resources/diffs/parser/04.diff b/src/test/resources/diffs/parser/04.diff
new file mode 100644
index 000000000..29e4d6b1c
--- /dev/null
+++ b/src/test/resources/diffs/parser/04.diff
@@ -0,0 +1,4 @@
+ #ifdef A
+ a \
+ b
+ #endif
diff --git a/src/test/resources/diffs/parser/04_expected.lg b/src/test/resources/diffs/parser/04_expected.lg
new file mode 100644
index 000000000..f5c884bcc
--- /dev/null
+++ b/src/test/resources/diffs/parser/04_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 4, diff: 4, new:4);A;#ifdef A
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:2);(old: 4, diff: 4, new:4);; a \; b
+e 144 16 ba;0;0
+e 211 144 ba;0;0
diff --git a/src/test/resources/diffs/parser/05.diff b/src/test/resources/diffs/parser/05.diff
new file mode 100644
index 000000000..29e4d6b1c
--- /dev/null
+++ b/src/test/resources/diffs/parser/05.diff
@@ -0,0 +1,4 @@
+ #ifdef A
+ a \
+ b
+ #endif
diff --git a/src/test/resources/diffs/parser/05_expected.lg b/src/test/resources/diffs/parser/05_expected.lg
new file mode 100644
index 000000000..f5c884bcc
--- /dev/null
+++ b/src/test/resources/diffs/parser/05_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 4, diff: 4, new:4);A;#ifdef A
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:2);(old: 4, diff: 4, new:4);; a \; b
+e 144 16 ba;0;0
+e 211 144 ba;0;0
diff --git a/src/test/resources/diffs/parser/06.diff b/src/test/resources/diffs/parser/06.diff
new file mode 100644
index 000000000..68cfda5df
--- /dev/null
+++ b/src/test/resources/diffs/parser/06.diff
@@ -0,0 +1,4 @@
+ // hehe \
+ #ifdef A
+ Code \
+ #endif
diff --git a/src/test/resources/diffs/parser/06_expected.lg b/src/test/resources/diffs/parser/06_expected.lg
new file mode 100644
index 000000000..3db6fa39c
--- /dev/null
+++ b/src/test/resources/diffs/parser/06_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 147 NON;ARTIFACT;(old: 1, diff: 1, new:1);(old: 3, diff: 3, new:3);;// hehe \;#ifdef A
+v 275 NON;ARTIFACT;(old: 3, diff: 3, new:3);(old: 5, diff: 5, new:5);;Code \;#endif
+e 147 16 ba;0;0
+e 275 16 ba;1;1
diff --git a/src/test/resources/diffs/parser/07.diff b/src/test/resources/diffs/parser/07.diff
new file mode 100644
index 000000000..4377a1cb6
--- /dev/null
+++ b/src/test/resources/diffs/parser/07.diff
@@ -0,0 +1,2 @@
+-a \
+ b
diff --git a/src/test/resources/diffs/parser/07_expected.lg b/src/test/resources/diffs/parser/07_expected.lg
new file mode 100644
index 000000000..1923b2dc1
--- /dev/null
+++ b/src/test/resources/diffs/parser/07_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 139 REM;ARTIFACT;(old: 1, diff: 1, new:-1);(old: 3, diff: 3, new:-1);;a \;b
+v 195 ADD;ARTIFACT;(old: -1, diff: 2, new:1);(old: -1, diff: 3, new:2);;b
+e 139 16 b;0
+e 195 16 a;1
diff --git a/src/test/resources/diffs/parser/08.diff b/src/test/resources/diffs/parser/08.diff
new file mode 100644
index 000000000..77b982282
--- /dev/null
+++ b/src/test/resources/diffs/parser/08.diff
@@ -0,0 +1,4 @@
+-#ifdef A
++#ifdef B
+ Code
+ #endif
diff --git a/src/test/resources/diffs/parser/08_expected.lg b/src/test/resources/diffs/parser/08_expected.lg
new file mode 100644
index 000000000..f03adcf72
--- /dev/null
+++ b/src/test/resources/diffs/parser/08_expected.lg
@@ -0,0 +1,8 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 3, diff: 4, new:-1);A;#ifdef A
+v 275 NON;ARTIFACT;(old: 2, diff: 3, new:2);(old: 3, diff: 4, new:3);;Code
+v 192 ADD;IF;(old: -1, diff: 2, new:1);(old: -1, diff: 4, new:3);B;#ifdef B
+e 136 16 b;0
+e 275 136 b;0
+e 275 192 a;0
+e 192 16 a;1
diff --git a/src/test/resources/diffs/parser/09.diff b/src/test/resources/diffs/parser/09.diff
new file mode 100644
index 000000000..5d2f2bd7a
--- /dev/null
+++ b/src/test/resources/diffs/parser/09.diff
@@ -0,0 +1,5 @@
+ #ifdef A
+ Code 1
+-#endif
+ Code 2
++#endif
diff --git a/src/test/resources/diffs/parser/09_expected.lg b/src/test/resources/diffs/parser/09_expected.lg
new file mode 100644
index 000000000..eec645c42
--- /dev/null
+++ b/src/test/resources/diffs/parser/09_expected.lg
@@ -0,0 +1,8 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 3, diff: 5, new:4);A;#ifdef A
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:2);(old: 3, diff: 3, new:3);;Code 1
+v 339 NON;ARTIFACT;(old: 4, diff: 4, new:3);(old: 5, diff: 5, new:4);;Code 2
+e 144 16 ba;0;0
+e 211 144 ba;0;0
+e 339 16 b;1
+e 339 144 a;1
diff --git a/src/test/resources/diffs/parser/10.diff b/src/test/resources/diffs/parser/10.diff
new file mode 100644
index 000000000..d278e57a4
--- /dev/null
+++ b/src/test/resources/diffs/parser/10.diff
@@ -0,0 +1,5 @@
++#ifdef B
+-#ifdef A
+ Code
+-#endif
++#endif // B
diff --git a/src/test/resources/diffs/parser/10_expected.lg b/src/test/resources/diffs/parser/10_expected.lg
new file mode 100644
index 000000000..522c47316
--- /dev/null
+++ b/src/test/resources/diffs/parser/10_expected.lg
@@ -0,0 +1,8 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 128 ADD;IF;(old: -1, diff: 1, new:1);(old: -1, diff: 5, new:3);B;#ifdef B
+v 275 NON;ARTIFACT;(old: 2, diff: 3, new:2);(old: 3, diff: 4, new:3);;Code
+v 200 REM;IF;(old: 1, diff: 2, new:-1);(old: 3, diff: 4, new:-1);A;#ifdef A
+e 128 16 a;0
+e 275 200 b;0
+e 275 128 a;0
+e 200 16 b;1
diff --git a/src/test/resources/diffs/parser/11.diff b/src/test/resources/diffs/parser/11.diff
new file mode 100644
index 000000000..b4f556535
--- /dev/null
+++ b/src/test/resources/diffs/parser/11.diff
@@ -0,0 +1,5 @@
+-#ifdef A
++#ifdef B
+ Code
+-#endif
++#endif // B
diff --git a/src/test/resources/diffs/parser/11_expected.lg b/src/test/resources/diffs/parser/11_expected.lg
new file mode 100644
index 000000000..2aa304f8d
--- /dev/null
+++ b/src/test/resources/diffs/parser/11_expected.lg
@@ -0,0 +1,8 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 3, diff: 4, new:-1);A;#ifdef A
+v 275 NON;ARTIFACT;(old: 2, diff: 3, new:2);(old: 3, diff: 4, new:3);;Code
+v 192 ADD;IF;(old: -1, diff: 2, new:1);(old: -1, diff: 5, new:3);B;#ifdef B
+e 136 16 b;0
+e 275 136 b;0
+e 275 192 a;0
+e 192 16 a;1
diff --git a/src/test/resources/diffs/parser/12.diff b/src/test/resources/diffs/parser/12.diff
new file mode 100644
index 000000000..0e1892404
--- /dev/null
+++ b/src/test/resources/diffs/parser/12.diff
@@ -0,0 +1,3 @@
+ #if defined(A)
+ Code
+ #endif
diff --git a/src/test/resources/diffs/parser/12_expected.lg b/src/test/resources/diffs/parser/12_expected.lg
new file mode 100644
index 000000000..d1e8054a8
--- /dev/null
+++ b/src/test/resources/diffs/parser/12_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 3, diff: 3, new:3);A;#if defined(A)
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:2);(old: 3, diff: 3, new:3);;Code
+e 144 16 ba;0;0
+e 211 144 ba;0;0
diff --git a/src/test/resources/diffs/parser/13.diff b/src/test/resources/diffs/parser/13.diff
new file mode 100644
index 000000000..4c143fa45
--- /dev/null
+++ b/src/test/resources/diffs/parser/13.diff
@@ -0,0 +1,3 @@
+ #ifdef A
+ #else // B
+ #endif
diff --git a/src/test/resources/diffs/parser/13_expected.lg b/src/test/resources/diffs/parser/13_expected.lg
new file mode 100644
index 000000000..c6ab93d6e
--- /dev/null
+++ b/src/test/resources/diffs/parser/13_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 2, diff: 2, new:2);A;#ifdef A
+v 209 NON;ELSE;(old: 2, diff: 2, new:2);(old: 3, diff: 3, new:3);;#else // B
+e 144 16 ba;0;0
+e 209 144 ba;0;0
diff --git a/src/test/resources/diffs/parser/14.diff b/src/test/resources/diffs/parser/14.diff
new file mode 100644
index 000000000..c47039691
--- /dev/null
+++ b/src/test/resources/diffs/parser/14.diff
@@ -0,0 +1,3 @@
+ #ifdef A
+-#else // B
+ #endif
diff --git a/src/test/resources/diffs/parser/14_expected.lg b/src/test/resources/diffs/parser/14_expected.lg
new file mode 100644
index 000000000..068ae3d90
--- /dev/null
+++ b/src/test/resources/diffs/parser/14_expected.lg
@@ -0,0 +1,5 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 2, diff: 3, new:2);A;#ifdef A
+v 201 REM;ELSE;(old: 2, diff: 2, new:-1);(old: 3, diff: 3, new:-1);;#else // B
+e 144 16 ba;0;0
+e 201 144 b;0
diff --git a/src/test/resources/diffs/parser/15.diff b/src/test/resources/diffs/parser/15.diff
new file mode 100644
index 000000000..fb44d3df0
--- /dev/null
+++ b/src/test/resources/diffs/parser/15.diff
@@ -0,0 +1,4 @@
+ #ifdef A
+-#elif \
+ B
+ #endif
diff --git a/src/test/resources/diffs/parser/15_expected.lg b/src/test/resources/diffs/parser/15_expected.lg
new file mode 100644
index 000000000..b3c35d57e
--- /dev/null
+++ b/src/test/resources/diffs/parser/15_expected.lg
@@ -0,0 +1,7 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 2, diff: 4, new:3);A;#ifdef A
+v 202 REM;ELIF;(old: 2, diff: 2, new:-1);(old: 4, diff: 4, new:-1);B;#elif \; B
+v 259 ADD;ARTIFACT;(old: -1, diff: 3, new:2);(old: -1, diff: 4, new:3);; B
+e 144 16 ba;0;0
+e 202 144 b;0
+e 259 144 a;1
diff --git a/src/test/resources/diffs/parser/16.diff b/src/test/resources/diffs/parser/16.diff
new file mode 100644
index 000000000..e924f3444
--- /dev/null
+++ b/src/test/resources/diffs/parser/16.diff
@@ -0,0 +1,10 @@
+-#ifdef A
+ a
++#ifdef B
+ b
++#elif C
+ c
+ #else D
+ d
+-#endif
++#endif // D
diff --git a/src/test/resources/diffs/parser/16_expected.lg b/src/test/resources/diffs/parser/16_expected.lg
new file mode 100644
index 000000000..61be20264
--- /dev/null
+++ b/src/test/resources/diffs/parser/16_expected.lg
@@ -0,0 +1,21 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 5, diff: 7, new:-1);A;#ifdef A
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:1);(old: 3, diff: 3, new:2);;a
+v 339 NON;ARTIFACT;(old: 3, diff: 4, new:3);(old: 4, diff: 5, new:4);;b
+v 467 NON;ARTIFACT;(old: 4, diff: 6, new:5);(old: 5, diff: 7, new:6);;c
+v 529 NON;ELSE;(old: 5, diff: 7, new:6);(old: 7, diff: 10, new:8);;#else D
+v 595 NON;ARTIFACT;(old: 6, diff: 8, new:7);(old: 7, diff: 9, new:8);;d
+v 256 ADD;IF;(old: -1, diff: 3, new:2);(old: -1, diff: 5, new:4);B;#ifdef B
+v 386 ADD;ELIF;(old: -1, diff: 5, new:4);(old: -1, diff: 7, new:6);C;#elif C
+e 136 16 b;0
+e 211 136 b;0
+e 211 16 a;1
+e 339 136 b;1
+e 339 256 a;0
+e 467 136 b;2
+e 467 386 a;0
+e 529 136 b;3
+e 529 386 a;1
+e 595 529 ba;0;0
+e 256 16 a;2
+e 386 256 a;1
diff --git a/src/test/resources/diffs/parser/17.diff b/src/test/resources/diffs/parser/17.diff
new file mode 100644
index 000000000..792600fa9
--- /dev/null
+++ b/src/test/resources/diffs/parser/17.diff
@@ -0,0 +1,5 @@
+-#ifdef A
++#ifdef B #\
+ #else
++#endif
+-#endif
diff --git a/src/test/resources/diffs/parser/17_expected.lg b/src/test/resources/diffs/parser/17_expected.lg
new file mode 100644
index 000000000..c6bb75516
--- /dev/null
+++ b/src/test/resources/diffs/parser/17_expected.lg
@@ -0,0 +1,7 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 136 REM;IF;(old: 1, diff: 1, new:-1);(old: 2, diff: 3, new:-1);A;#ifdef A
+v 265 REM;ELSE;(old: 2, diff: 3, new:-1);(old: 3, diff: 5, new:-1);;#else
+v 192 ADD;IF;(old: -1, diff: 2, new:1);(old: -1, diff: 4, new:3);B##else;#ifdef B #\;#else
+e 136 16 b;0
+e 265 136 b;0
+e 192 16 a;1
diff --git a/src/test/resources/diffs/parser/18.diff b/src/test/resources/diffs/parser/18.diff
new file mode 100644
index 000000000..0163672b8
--- /dev/null
+++ b/src/test/resources/diffs/parser/18.diff
@@ -0,0 +1,2 @@
+ // Comment \
+ #ifdef A
diff --git a/src/test/resources/diffs/parser/18_expected.lg b/src/test/resources/diffs/parser/18_expected.lg
new file mode 100644
index 000000000..547cadeaa
--- /dev/null
+++ b/src/test/resources/diffs/parser/18_expected.lg
@@ -0,0 +1,3 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 147 NON;ARTIFACT;(old: 1, diff: 1, new:1);(old: 3, diff: 3, new:3);;// Comment \;#ifdef A
+e 147 16 ba;0;0
diff --git a/src/test/resources/diffs/parser/wontfix/01.diff b/src/test/resources/diffs/parser/wontfix/01.diff
new file mode 100644
index 000000000..e08b3d2d5
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/01.diff
@@ -0,0 +1,3 @@
+ # \
+ ifdef A
+ #endif
diff --git a/src/test/resources/diffs/parser/wontfix/01_expected.lg b/src/test/resources/diffs/parser/wontfix/01_expected.lg
new file mode 100644
index 000000000..b37a4fe9e
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/01_expected.lg
@@ -0,0 +1,3 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 3, diff: 3, new:3);A;# \; ifdef A
+e 144 16 ba;0;0
diff --git a/src/test/resources/diffs/parser/wontfix/02.diff b/src/test/resources/diffs/parser/wontfix/02.diff
new file mode 100644
index 000000000..3f0c241eb
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/02.diff
@@ -0,0 +1,2 @@
+ # /* Comment */ ifdef A
+ #endif
diff --git a/src/test/resources/diffs/parser/wontfix/02_expected.lg b/src/test/resources/diffs/parser/wontfix/02_expected.lg
new file mode 100644
index 000000000..e898d4807
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/02_expected.lg
@@ -0,0 +1,3 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 2, diff: 2, new:2);A;# /* Comment */ ifdef A
+e 144 16 ba;0;0
diff --git a/src/test/resources/diffs/parser/wontfix/03.diff b/src/test/resources/diffs/parser/wontfix/03.diff
new file mode 100644
index 000000000..352827574
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/03.diff
@@ -0,0 +1,3 @@
+ /* This is a comment, so it's removed before the preprocessor is run
+ #ifdef A
+ */
diff --git a/src/test/resources/diffs/parser/wontfix/03_expected.lg b/src/test/resources/diffs/parser/wontfix/03_expected.lg
new file mode 100644
index 000000000..65da8a078
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/03_expected.lg
@@ -0,0 +1,7 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 147 NON;ARTIFACT;(old: 1, diff: 1, new:1);(old: 2, diff: 2, new:2);;/* This is a comment, so it's removed before the preprocessor is run
+v 211 NON;ARTIFACT;(old: 2, diff: 2, new:2);(old: 3, diff: 3, new:3);; #ifdef A
+v 275 NON;ARTIFACT;(old: 3, diff: 3, new:3);(old: 4, diff: 4, new:4);; */
+e 147 16 ba;0;0
+e 211 16 ba;1;1
+e 275 16 ba;2;2
diff --git a/src/test/resources/diffs/parser/wontfix/04.diff b/src/test/resources/diffs/parser/wontfix/04.diff
new file mode 100644
index 000000000..55acbcfc2
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/04.diff
@@ -0,0 +1,4 @@
+ #ifdef /*
+ * Comment
+ */ A
+ #endif
diff --git a/src/test/resources/diffs/parser/wontfix/04_expected.lg b/src/test/resources/diffs/parser/wontfix/04_expected.lg
new file mode 100644
index 000000000..33e0a085e
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/04_expected.lg
@@ -0,0 +1,3 @@
+v 16 NON;IF;(old: -1, diff: -1, new:-1);(old: -1, diff: -1, new:-1);True
+v 144 NON;IF;(old: 1, diff: 1, new:1);(old: 4, diff: 4, new:4);A;#ifdef /*; * Comment; */ A
+e 144 16 ba;0;0
diff --git a/src/test/resources/diffs/parser/wontfix/README.md b/src/test/resources/diffs/parser/wontfix/README.md
new file mode 100644
index 000000000..5c016ea6e
--- /dev/null
+++ b/src/test/resources/diffs/parser/wontfix/README.md
@@ -0,0 +1,9 @@
+# Test cases of WONTFIX behaviour
+
+## Sophisticated comment parsing
+The test cases 01 and 02 would require the removal of comments before checking
+if a line is a macro. The test cases 03 and 04 would required the detection of
+multi-line macros which span multiple lines.
+
+All of these cases require more engineering than they are worth because they
+should be pretty rare in practice.
diff --git a/src/test/resources/multilinemacros/diffWithComments.txt b/src/test/resources/multilinemacros/diffWithComments.txt
index 468926863..196f062c1 100644
--- a/src/test/resources/multilinemacros/diffWithComments.txt
+++ b/src/test/resources/multilinemacros/diffWithComments.txt
@@ -1,10 +1,11 @@
+#define multi \
+kulti
-
+
/** hihi **\
-
+
//\
-
+ a
+
+#if A & B
foo();
-+#endif
\ No newline at end of file
++#endif
diff --git a/src/test/resources/patterns/elementary.diff b/src/test/resources/patterns/elementary.diff
index 5ea837d20..ee545f137 100644
--- a/src/test/resources/patterns/elementary.diff
+++ b/src/test/resources/patterns/elementary.diff
@@ -1,39 +1,39 @@
#if root
-
+
+#if a
+AddWithMapping
+#endif
-
+
#if a
+AddToPC
#endif
-
+
-#if a
-RemWithMapping
-#endif
-
+
#if a
-RemFromPC
#endif
-
+
+#if a
Specialization
+#endif
-
+
-#if a
Generalization
-#endif
-
+
+#if a
-#if b
Reconfiguration
#endif
-
+
+#if a
-#if (a && 1) || false
Refactoring
#endif
-
+
Untouched
-
- #endif
\ No newline at end of file
+
+ #endif
diff --git a/src/test/resources/serialize/.gitignore b/src/test/resources/serialize/.gitignore
new file mode 100644
index 000000000..87dba571b
--- /dev/null
+++ b/src/test/resources/serialize/.gitignore
@@ -0,0 +1 @@
+actual.tex