Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Enhanced Diff Test Selection #981

Merged
merged 12 commits into from
Dec 22, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import eu.stamp_project.diff_test_selection.clover.CloverReader;
import eu.stamp_project.diff_test_selection.configuration.Configuration;
import eu.stamp_project.diff_test_selection.configuration.Options;
import eu.stamp_project.diff_test_selection.coverage.Coverage;
import eu.stamp_project.diff_test_selection.selector.DiffTestSelection;
import eu.stamp_project.diff_test_selection.selector.DiffTestSelectionImpl;
import eu.stamp_project.diff_test_selection.selector.EnhancedDiffTestSelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -23,22 +27,57 @@ public static void main(String[] args) {
}

public static void run(Configuration configuration) {
final Map<String, Map<String, Map<String, List<Integer>>>> coverage = getCoverage(configuration.pathToFirstVersion);
final DiffTestSelection diffTestSelection = new DiffTestSelection(configuration, coverage);
final Map<String, Set<String>> testThatExecuteChanges = diffTestSelection.getTestThatExecuteChanges();
final Map<String, Set<String>> selectedTests;
final Coverage coverage = new Coverage();
if (configuration.enhanced) {
LOGGER.info("Running in enhanced mode...");
selectedTests = enhancedRun(configuration, coverage);
} else {
LOGGER.info("Running...");
selectedTests = _run(configuration, coverage);
}
output(configuration, coverage, selectedTests);
}

public static Map<String, Set<String>> _run(Configuration configuration, Coverage coverage) {
final Map<String, Map<String, Map<String, List<Integer>>>> cloverCoverage = getCoverage(configuration.pathToFirstVersion);
final DiffTestSelection diffTestSelectionImpl = new DiffTestSelectionImpl(
configuration.pathToFirstVersion,
configuration.pathToSecondVersion,
cloverCoverage,
configuration.diff,
coverage
);
return diffTestSelectionImpl.selectTests();
}

private static Map<String, Set<String>> enhancedRun(Configuration configuration, Coverage coverage) {
final Map<String, Map<String, Map<String, List<Integer>>>> cloverCoverageV1 =
getCoverage(configuration.pathToFirstVersion);
final Map<String, Map<String, Map<String, List<Integer>>>> cloverCoverageV2 =
getCoverage(configuration.pathToSecondVersion);
return new EnhancedDiffTestSelection(
configuration.pathToFirstVersion,
configuration.pathToSecondVersion,
cloverCoverageV1,
configuration.diff,
coverage,
cloverCoverageV2
).selectTests();
}

private static void output(Configuration configuration, Coverage coverage, Map<String, Set<String>> selectedTests) {
LOGGER.info("Saving result in " + configuration.outputPath + " ...");
configuration.reportFormat.instance.report(
configuration.outputPath,
testThatExecuteChanges,
diffTestSelection.getCoverage()
selectedTests,
coverage
);
}

private static Map<String, Map<String, Map<String, List<Integer>>>> getCoverage(final String pathToFirstVersion) {
//if (!skipCoverage) {
LOGGER.info("Computing coverage for " + pathToFirstVersion);
new CloverExecutor().instrumentAndRunTest(pathToFirstVersion);
//}
LOGGER.info("Computing coverage for " + pathToFirstVersion);
new CloverExecutor().instrumentAndRunTest(pathToFirstVersion);
return new CloverReader().read(pathToFirstVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ private void setMavenHome() {
}
}

private String getMavenHome(Predicate<String> conditional,
Function<String, String> getFunction,
String... possibleValues) {
private String getMavenHome(Predicate<String> conditional,
Function<String, String> getFunction,
String... possibleValues) {
String mavenHome = null;
final Optional<String> potentialMavenHome = Arrays.stream(possibleValues).filter(conditional).findFirst();
if (potentialMavenHome.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class Configuration {

private static final Logger LOGGER = LoggerFactory.getLogger(Configuration.class);

private static final String SRC_FOLDER = "src";

public static final String DEFAULT_OUTPUT_PATH_NAME = "testsThatExecuteTheChange.csv";

public final String pathToFirstVersion;
Expand All @@ -30,23 +32,22 @@ public class Configuration {

public final ReportEnum reportFormat;

public final String module;

public final String diff;

public Configuration(String pathToFirstVersion, String pathToSecondVersion, String outputPath, String reportFormat, String module, String pathToDiff) {
public final boolean enhanced;

public Configuration(String pathToFirstVersion, String pathToSecondVersion, String outputPath, String reportFormat, String pathToDiff, boolean enhanced) {
this.pathToFirstVersion = pathToFirstVersion;
this.pathToSecondVersion = pathToSecondVersion;
this.reportFormat = ReportEnum.valueOf(reportFormat);
this.module = module == null ? "" : module;
if (pathToDiff == null || pathToDiff.isEmpty()) {
LOGGER.warn("No path to diff file has been specified.");
LOGGER.warn("I'll compute a diff file using the UNIX diff command");
LOGGER.warn("You may encounter troubles.");
LOGGER.warn("If so, please specify a path to a correct diff file");
LOGGER.warn("or implement a new way to compute a diff file.");
this.diff = new DiffComputer()
.computeDiffWithDiffCommand(new File(pathToFirstVersion), new File(pathToSecondVersion));
.computeDiffWithDiffCommand(new File(pathToFirstVersion + "/" + SRC_FOLDER), new File(pathToSecondVersion + "/" + SRC_FOLDER));
} else {
this.diff = this.readFile(pathToDiff);
}
Expand All @@ -57,6 +58,7 @@ public Configuration(String pathToFirstVersion, String pathToSecondVersion, Stri
} else {
this.outputPath = outputPath;
}
this.enhanced = enhanced;
}

private String readFile(String pathToFileToRead) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ public static final Configuration parse(String[] args) {
String pathToDiff = arguments.getString("path-to-diff");
final String outputPath = arguments.getString("output-path");
final String outputFormat = arguments.getString("output-format");
final String module = arguments.getString("module");
final boolean enhanced = arguments.getBoolean("enhanced");
return new Configuration(pathDirFirstVersion,
pathDirSecondVersion,
outputPath,
outputFormat,
module,
pathToDiff
pathToDiff,
enhanced
);
}

Expand Down Expand Up @@ -99,14 +99,6 @@ private static final JSAP initJSAP() {
outputFormat.setHelp("[Optional] Specify the format of the output. (For now, only the CSV format is available)");
outputFormat.setStringParser(JSAP.STRING_PARSER);

FlaggedOption module = new FlaggedOption("module");
module.setRequired(false);
module.setLongFlag("module");
module.setShortFlag('m');
module.setDefault("");
module.setHelp("[Optional] In case of multi-module project, specify which module (a path from the project's root).");
module.setStringParser(JSAP.STRING_PARSER);

FlaggedOption pathToDiff = new FlaggedOption("path-to-diff");
pathToDiff.setRequired(false);
pathToDiff.setLongFlag("path-to-diff");
Expand All @@ -115,13 +107,20 @@ private static final JSAP initJSAP() {
pathToDiff.setHelp("[Optional] Specify the path of a diff file. If it is not specified, it will be computed using diff command line.");
pathToDiff.setStringParser(JSAP.STRING_PARSER);

Switch enhanced = new Switch("enhanced");
enhanced.setDefault("true");
enhanced.setLongFlag("--enhanced");
enhanced.setShortFlag('c');
enhanced.setHelp("Use the enhanced diff-test-selection. Select the test of the first version that hit the deletions, and the " +
"tests of the second version that hit the additions.");

try {
jsap.registerParameter(pathDirectoryFirstVersion);
jsap.registerParameter(pathDirectorySecondVersion);
jsap.registerParameter(outputPath);
jsap.registerParameter(outputFormat);
jsap.registerParameter(module);
jsap.registerParameter(pathToDiff);
jsap.registerParameter(enhanced);
} catch (JSAPException e) {
e.printStackTrace();
usage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ public void covered(String fullQualifiedName, Integer line) {
}
}

public void addModifiedLines(String fullQualifiedName, List<Integer> lines) {
if (!this.modifiedLinePerQualifiedName.containsKey(fullQualifiedName)) {
this.modifiedLinePerQualifiedName.put(fullQualifiedName, new HashSet<>());
}
lines.forEach(this.modifiedLinePerQualifiedName.get(fullQualifiedName)::add);
LOGGER.info(fullQualifiedName + ":" + lines.toString() + " are modified.");
}

public void addModifiedLine(String fullQualifiedName, Integer line) {
if (!this.modifiedLinePerQualifiedName.containsKey(fullQualifiedName)) {
this.modifiedLinePerQualifiedName.put(fullQualifiedName, new HashSet<>());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package eu.stamp_project.diff_test_selection.diff;

import com.github.gumtreediff.actions.model.Action;
import com.github.gumtreediff.actions.model.Delete;
import com.github.gumtreediff.actions.model.Insert;
import eu.stamp_project.diff_test_selection.selector.DiffTestSelectionImpl;

import gumtree.spoon.AstComparator;
import gumtree.spoon.diff.Diff;
import gumtree.spoon.diff.operations.Operation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spoon.reflect.code.CtStatement;
import spoon.reflect.declaration.CtElement;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ModifiedLinesTool {

private static final Logger LOGGER = LoggerFactory.getLogger(ModifiedLinesTool.class);

private final String pathToFirstVersion;
private final String pathToSecondVersion;

private Map<String, List<Integer>> deletionPerQualifiedName;
private Map<String, List<Integer>> additionPerQualifiedName;

public ModifiedLinesTool(final String pathToFirstVersion, final String pathToSecondVersion) {
this.pathToFirstVersion = pathToFirstVersion;
this.pathToSecondVersion = pathToSecondVersion;
}

public Map<String, List<Integer>> getDeletionPerQualifiedName() {
return deletionPerQualifiedName;
}

public Map<String, List<Integer>> getAdditionPerQualifiedName() {
return additionPerQualifiedName;
}

public boolean hasResult() {
return this.deletionPerQualifiedName != null && this.additionPerQualifiedName != null;
}

public void compute(String currentLine, String secondLine) {
final File baseDir = new File(this.pathToFirstVersion);
final String file1 = ModifiedLinesUtils.getCorrectPathFile(currentLine);
final String file2 = ModifiedLinesUtils.getCorrectPathFile(secondLine);
if (ModifiedLinesUtils.shouldSkip(this.pathToFirstVersion, file1, file2)) {
LOGGER.warn("Could not match " + file1 + " and " + file2);
return;
}
final File f1 = ModifiedLinesUtils.getCorrectFile(baseDir.getAbsolutePath(), file1);
final File f2 = ModifiedLinesUtils.getCorrectFile(this.pathToSecondVersion, file2);
try {
LOGGER.info(f1.getAbsolutePath());
LOGGER.info(f2.getAbsolutePath());
this.buildMap(new AstComparator().compare(f1, f2));
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Error when trying to compare " + f1 + " and " + f2);
}
}

private void buildMap(Diff compare) {
this.additionPerQualifiedName = new LinkedHashMap<>();// keeps the order
this.deletionPerQualifiedName = new LinkedHashMap<>();// keeps the order
final List<Operation> allOperations = compare.getAllOperations();
final List<CtStatement> statements = new ArrayList<>();
for (Operation operation : allOperations) {
if (!isDeletionOrAddition(operation.getAction())) {
continue;
}
final CtElement node = ModifiedLinesUtils.filterOperation(operation);
if (node != null && !statements.contains(node.getParent(CtStatement.class))) {
final int line = node.getPosition().getLine();
final String qualifiedName = node
.getPosition()
.getCompilationUnit()
.getMainType()
.getQualifiedName();
this.addToCorrespondingMap(operation.getAction(), qualifiedName, line);
// TODO
// if (!(node.getParent(CtStatement.class) instanceof CtBlock<?>)) {
// this.coverage.addModifiedLine(qualifiedName, line);
// }
statements.add(node.getParent(CtStatement.class));
}
}
}

private void addToCorrespondingMap(Action action, String qualifiedName, int line) {
if (action instanceof Insert) {
this.addToGivenMap(this.additionPerQualifiedName, qualifiedName, line);
} else {
this.addToGivenMap(this.deletionPerQualifiedName, qualifiedName, line);
}
}

private void addToGivenMap(Map<String, List<Integer>> map, String qualifiedName, int line) {
if (!map.containsKey(qualifiedName)) {
map.put(qualifiedName, new ArrayList<>());
}
map.get(qualifiedName).add(line);
}

private boolean isDeletionOrAddition(Action action) {
return action instanceof Insert || action instanceof Delete;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package eu.stamp_project.diff_test_selection.diff;

import eu.stamp_project.diff_test_selection.utils.DiffTestSelectionUtils;
import gumtree.spoon.diff.operations.Operation;
import spoon.reflect.declaration.CtElement;

import java.io.File;

public class ModifiedLinesUtils {

public static CtElement filterOperation(Operation operation) {
if (operation.getSrcNode() != null &&
filterOperationFromNode(operation.getSrcNode())) {
return operation.getSrcNode();
} else if (operation.getDstNode() != null &&
filterOperationFromNode(operation.getDstNode())) {
return operation.getDstNode();
}
return null;
}

public static boolean filterOperationFromNode(CtElement element) {
return element.getPosition() != null &&
element.getPosition().getCompilationUnit() != null &&
element.getPosition().getCompilationUnit().getMainType() != null;
}

public static boolean shouldSkip(String pathToFirstVersion, String file1, String file2) {
if (file1.contains("src/test/java")) {
return true;
}
if (file2.endsWith(file1)) {
return false;
}
if (new File(file1).isAbsolute() && new File(file2).isAbsolute() &&
file2.endsWith(file1.substring(pathToFirstVersion.length()))) {
return false;
}
return true;
}

public static File getCorrectFile(String baseDir, String fileName) {
File file = new File(fileName);
if (file.isAbsolute() && file.exists()) {
return file;
}
// TODO
// if (fileName.substring(1).startsWith(this.configuration.module)) {
// fileName = fileName.substring(this.configuration.module.length() + 1);
// }
file = new File(baseDir + "/" + fileName);
return file.exists() ? file : new File(baseDir + "/../" + fileName);
}

public static String getCorrectPathFile(String path) {
return removeDiffPrefix(DiffTestSelectionUtils.getJavaFile(path));
}

public static String removeDiffPrefix(String s) {
return s.startsWith("a") || s.startsWith("b") ? s.substring(1) : s;
}

}
Loading