Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package se.isselab.testcasepropagation.codeCollection;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

public class ExternalToolRunner {

public static void runCommand(List<String> command, File workingDir) throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(workingDir);
builder.redirectErrorStream(true);
Process process = builder.start();

try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
reader.lines().forEach(System.out::println); // logging
}

int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command '" + String.join(" ", command) + "' failed with exit code " + exitCode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package se.isselab.testcasepropagation.codeCollection;

import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ForkToLocalMapper {

public record Mapping(String forkTest, String forkUUT, String localTest, String localUUT) {}

public static List<Mapping> generateMappings(File forkXml, File simianReport) throws ParserConfigurationException, IOException, SAXException {
List<UUTExtractor.TestMethodUUTs> forkUUTs = UUTExtractor.extractUUTsFromSrcML(forkXml);
List<SimianCloneMapper.CloneMatch> cloneMatches = SimianCloneMapper.parseSimianOutput(simianReport);
Map<String, String> forkToLocalMap = SimianCloneMapper.mapForkToLocalFromClones(cloneMatches);

List<Mapping> mappings = new ArrayList<>();
for (UUTExtractor.TestMethodUUTs method : forkUUTs) {
String forkTest = forkXml.getName().replace(".xml", "");
String localTest = forkToLocalMap.getOrDefault(forkTest, "UNKOWN");

for (String uut : method.uutCandidates()) {
String localUUT = forkToLocalMap.getOrDefault(uut, "UNKOWN");
mappings.add(new Mapping(forkTest, uut, localTest, localUUT));
}
}
return mappings;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package se.isselab.testcasepropagation.codeCollection;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SimianCloneMapper {

public record CloneMatch(String forkFile, int forkStartLine, String localFile, int localStartLine, int lineCount) {}

private static final Pattern matchPattern = Pattern.compile("Found (\\d+) duplicate lines in:(.*?)\\n\\n", Pattern.DOTALL);

public static List<CloneMatch> parseSimianOutput(File simianOutput) throws IOException {
String content = Files.readString(simianOutput.toPath());
List<CloneMatch> matches = new ArrayList<>();

Matcher matcher = matchPattern.matcher(content);
while (matcher.find()) {
String block = matcher.group(2).trim();
String[] lines = block.split("\n");
if (lines.length >= 2) {
String[] forkInfo = lines[0].split(":");
String[] localInfo = lines[1].split(":");
matches.add(new CloneMatch(
forkInfo[0].trim(),
Integer.parseInt(forkInfo[1].split("-")[0]),
localInfo[0].trim(),
Integer.parseInt(localInfo[1].split("-")[0]),
Integer.parseInt(matcher.group(1))
));
}
}
return matches;
}

public static Map<String, String> mapForkToLocalFromClones(List<CloneMatch> matches) {
Map<String, String> map = new HashMap<>();
for (CloneMatch match : matches) {
map.putIfAbsent(match.forkFile(), match.localFile());
}
return map;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package se.isselab.testcasepropagation.codeCollection;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class SimianRunner {

public static File runSimian(File codeDir, File outputReport) throws IOException, InterruptedException {
List<String> command = List.of(
"java", "-jar", "PATH_TO_SIMIAN.JAR",
"-includes=**/*.java",
"-formatter=plain:" + outputReport.getAbsolutePath()
);

ExternalToolRunner.runCommand(command, codeDir);

return outputReport;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package se.isselab.testcasepropagation.codeCollection;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class SrcMLRunner {

public static File convertToXml(File javaFile, File outputDir) throws IOException, InterruptedException {
File outputXml = new File(outputDir, javaFile.getName() + ".xml");

List<String> command = List.of(
"srcml", javaFile.getAbsolutePath(), "-o", outputXml.getAbsolutePath()
);

ExternalToolRunner.runCommand(command, javaFile.getParentFile());

return outputXml;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package se.isselab.testcasepropagation.codeCollection;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.*;

public class UUTExtractor {

public record TestMethodUUTs(String testMethodName, Set<String> uutCandidates) {}

public static List<TestMethodUUTs> extractUUTsFromSrcML(File srcmlXmlFile) throws ParserConfigurationException, IOException, SAXException {
List<TestMethodUUTs> result = new ArrayList<>();

DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = db.parse(srcmlXmlFile);

NodeList functions = doc.getElementsByTagName("function");

for (int i = 0; i < functions.getLength(); i++) {
Element function = (Element) functions.item(i);

String methodName = getChildText(function, "name");

Set<String> uuts = new HashSet<>();
Map<String, String> variableToClass = new HashMap<>();

// Variable declarations
NodeList declStmts = function.getElementsByTagName("decl_stmt");
for (int j = 0; j < declStmts.getLength(); j++) {
Element declStmt = (Element) declStmts.item(j);

NodeList decls = declStmt.getElementsByTagName("decl");
for (int k = 0; k < decls.getLength(); k++) {
Element decl = (Element) decls.item(k);

NodeList nameNodes = decl.getElementsByTagName("name");
if (nameNodes.getLength() >= 2) {
String className = nameNodes.item(0).getTextContent().trim();
String varName = nameNodes.item(1).getTextContent().trim();

variableToClass.put(varName, className);
};
}
}

// Method calls
NodeList calls = function.getElementsByTagName("call");
for (int j = 0; j < calls.getLength(); j++) {
Element call = (Element) calls.item(j);
String callText = call.getTextContent().trim();
String[] parts = callText.split("\\.");
if (parts.length > 1) {
String prefix = parts[0]; // TODO: good to find class file but bad for: StaticOuter.StaticInner.someMethod()
if (variableToClass.containsKey(prefix)) {
uuts.add(variableToClass.get(prefix));
} else {
uuts.add(prefix);
}
}
}

// Method references
NodeList exprs = function.getElementsByTagName("expr");
for (int j = 0; j < exprs.getLength(); j++) {
Element expr = (Element) exprs.item(j);

NodeList ops = expr.getElementsByTagName("operator");
for (int k = 0; k < ops.getLength(); k++) {
if ("::".equals(ops.item(k).getTextContent().trim())) {
NodeList names = expr.getElementsByTagName("name");
if (names.getLength() >= 1) {
String nameText = names.item(0).getTextContent().trim();

String prefix;
if (nameText.contains(".")) {
prefix = nameText.split("\\.")[0];
} else {
prefix = nameText;
}

if (variableToClass.containsKey(prefix)) {
uuts.add(variableToClass.get(prefix));
} else {
uuts.add(prefix);
}
}
}
}
}

result.add(new TestMethodUUTs(methodName, uuts));
}

return result;
}

private static String getChildText(Element element, String tagName) {
NodeList nl = element.getElementsByTagName(tagName);
if (nl.getLength() == 0) return null;
return nl.item(0).getTextContent().trim();
}
}
Loading