From 07e5a7c01bb5bbe405b0f1c19242f384f334b4a6 Mon Sep 17 00:00:00 2001 From: fractalcanopy Date: Tue, 24 Jun 2025 17:23:57 +0200 Subject: [PATCH 1/4] implemented uut extractor for srcML --- .../codeCollection/UUTExtractor.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/main/java/se/isselab/testcasepropagation/codeCollection/UUTExtractor.java diff --git a/src/main/java/se/isselab/testcasepropagation/codeCollection/UUTExtractor.java b/src/main/java/se/isselab/testcasepropagation/codeCollection/UUTExtractor.java new file mode 100644 index 0000000..d151ffd --- /dev/null +++ b/src/main/java/se/isselab/testcasepropagation/codeCollection/UUTExtractor.java @@ -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 uutCandidates) {} + + public static List extractUUTsFromSrcML(File srcmlXmlFile) throws ParserConfigurationException, IOException, SAXException { + List 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 uuts = new HashSet<>(); + Map 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(); + } +} From cab91003fba6a0afe366e359a4b8dfab3a2005e4 Mon Sep 17 00:00:00 2001 From: fractalcanopy Date: Tue, 24 Jun 2025 18:50:28 +0200 Subject: [PATCH 2/4] created simian clone mapper (very buggy) --- .../codeCollection/SimianCloneMapper.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/se/isselab/testcasepropagation/codeCollection/SimianCloneMapper.java diff --git a/src/main/java/se/isselab/testcasepropagation/codeCollection/SimianCloneMapper.java b/src/main/java/se/isselab/testcasepropagation/codeCollection/SimianCloneMapper.java new file mode 100644 index 0000000..463a895 --- /dev/null +++ b/src/main/java/se/isselab/testcasepropagation/codeCollection/SimianCloneMapper.java @@ -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 parseSimianOutput(File simianOutput) throws IOException { + String content = Files.readString(simianOutput.toPath()); + List 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 mapForkToLocalFromClones(List matches) { + Map map = new HashMap<>(); + for (CloneMatch match : matches) { + map.putIfAbsent(match.forkFile(), match.localFile()); + } + return map; + } +} From a94c86bdf2f7b6933d2976bc38d0a3386bc0a47b Mon Sep 17 00:00:00 2001 From: fractalcanopy Date: Tue, 24 Jun 2025 19:05:06 +0200 Subject: [PATCH 3/4] created srcML and simian integration class (very buggy) --- .../codeCollection/ForkToLocalMapper.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/se/isselab/testcasepropagation/codeCollection/ForkToLocalMapper.java diff --git a/src/main/java/se/isselab/testcasepropagation/codeCollection/ForkToLocalMapper.java b/src/main/java/se/isselab/testcasepropagation/codeCollection/ForkToLocalMapper.java new file mode 100644 index 0000000..22622e7 --- /dev/null +++ b/src/main/java/se/isselab/testcasepropagation/codeCollection/ForkToLocalMapper.java @@ -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 generateMappings(File forkXml, File simianReport) throws ParserConfigurationException, IOException, SAXException { + List forkUUTs = UUTExtractor.extractUUTsFromSrcML(forkXml); + List cloneMatches = SimianCloneMapper.parseSimianOutput(simianReport); + Map forkToLocalMap = SimianCloneMapper.mapForkToLocalFromClones(cloneMatches); + + List 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; + } +} From 0413f1169eb81779024c5c991ebc50a12f5a5fd3 Mon Sep 17 00:00:00 2001 From: fractalcanopy Date: Tue, 24 Jun 2025 19:34:07 +0200 Subject: [PATCH 4/4] created local code running functionality --- .../codeCollection/ExternalToolRunner.java | 26 +++++++++++++++++++ .../codeCollection/SimianRunner.java | 20 ++++++++++++++ .../codeCollection/SrcMLRunner.java | 21 +++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 src/main/java/se/isselab/testcasepropagation/codeCollection/ExternalToolRunner.java create mode 100644 src/main/java/se/isselab/testcasepropagation/codeCollection/SimianRunner.java create mode 100644 src/main/java/se/isselab/testcasepropagation/codeCollection/SrcMLRunner.java diff --git a/src/main/java/se/isselab/testcasepropagation/codeCollection/ExternalToolRunner.java b/src/main/java/se/isselab/testcasepropagation/codeCollection/ExternalToolRunner.java new file mode 100644 index 0000000..df92140 --- /dev/null +++ b/src/main/java/se/isselab/testcasepropagation/codeCollection/ExternalToolRunner.java @@ -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 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); + } + } +} diff --git a/src/main/java/se/isselab/testcasepropagation/codeCollection/SimianRunner.java b/src/main/java/se/isselab/testcasepropagation/codeCollection/SimianRunner.java new file mode 100644 index 0000000..534d6ba --- /dev/null +++ b/src/main/java/se/isselab/testcasepropagation/codeCollection/SimianRunner.java @@ -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 command = List.of( + "java", "-jar", "PATH_TO_SIMIAN.JAR", + "-includes=**/*.java", + "-formatter=plain:" + outputReport.getAbsolutePath() + ); + + ExternalToolRunner.runCommand(command, codeDir); + + return outputReport; + } +} diff --git a/src/main/java/se/isselab/testcasepropagation/codeCollection/SrcMLRunner.java b/src/main/java/se/isselab/testcasepropagation/codeCollection/SrcMLRunner.java new file mode 100644 index 0000000..539ab5e --- /dev/null +++ b/src/main/java/se/isselab/testcasepropagation/codeCollection/SrcMLRunner.java @@ -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 command = List.of( + "srcml", javaFile.getAbsolutePath(), "-o", outputXml.getAbsolutePath() + ); + + ExternalToolRunner.runCommand(command, javaFile.getParentFile()); + + return outputXml; + } +}