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