Skip to content

Commit

Permalink
Implementing Mining Framework for S3M handlers analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
jvcoutinho committed Nov 20, 2019
1 parent cc1d58d commit 6900788
Show file tree
Hide file tree
Showing 16 changed files with 855 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/services/S3MHandlersAnalysis/Handlers.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package services.S3MHandlersAnalysis

enum Handlers {
Renaming

static final Map<Integer, String> mergeResultPaths = [0: 'textual.java', 1: 'Renaming/CT.java', 2: 'Renaming/SF.java', 3: 'Renaming/MM.java', 4: 'Renaming/KB.java']
static final Map<Integer, String> mergeAlgorithms = [0: 'TM', 1: 'CT', 2: 'SF', 3: 'MM', 4: 'KB']

}
25 changes: 25 additions & 0 deletions src/services/S3MHandlersAnalysis/MiningModule.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package services.S3MHandlersAnalysis

@Grab('com.google.inject:guice:4.2.2')
import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import main.interfaces.DataCollector
import services.S3MHandlersAnalysis.implementations.CommitFilter
import services.S3MHandlersAnalysis.implementations.MergesCollector
import services.S3MHandlersAnalysis.implementations.OutputProcessor
import services.S3MHandlersAnalysis.implementations.ProjectProcessor

class MiningModule extends AbstractModule {

@Override
protected void configure() {
Multibinder<DataCollector> dataCollectorBinder = Multibinder.newSetBinder(binder(), DataCollector.class)

dataCollectorBinder.addBinding().to(MergesCollector.class)

bind(main.interfaces.CommitFilter.class).to(CommitFilter.class)
bind(main.interfaces.ProjectProcessor.class).to(ProjectProcessor.class)
bind(main.interfaces.OutputProcessor.class).to(OutputProcessor.class)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package services.S3MHandlersAnalysis.datacollection

import main.project.MergeCommit
import main.project.Project
import services.S3MHandlersAnalysis.Handlers
import services.S3MHandlersAnalysis.util.BuildRequester
import services.S3MHandlersAnalysis.util.MergeCommitSummary
import services.S3MHandlersAnalysis.util.MergeScenarioSummary

import java.nio.file.Path

class DataAnalyser {

static MergeCommitSummary analyseScenarios(Project project, MergeCommit mergeCommit, List<Path> mergeScenarios) {
MergeCommitSummary summary = new MergeCommitSummary()
buildCommitSummary(summary, mergeScenarios)

checkForFalseNegatives(project, mergeCommit, mergeScenarios, summary)
return summary
}

private static void buildCommitSummary(MergeCommitSummary summary, List<Path> mergeScenarios) {
mergeScenarios.stream()
.map(MergeScenarioSummary::new)
.forEach(summary::addMergeSummary)
}

private static void checkForFalseNegatives(Project project, MergeCommit mergeCommit, List<Path> mergeScenarios, MergeCommitSummary summary) {
summary.numberOfConflicts.eachWithIndex { int numConflicts, int i ->
if (numConflicts == 0 && !summary.handlersHaveSameConflicts) {
// there's a merge result with at least one conflict
String buildLink = BuildRequester.requestBuildWithRevision(project, mergeCommit, mergeScenarios, i)
summary.markAsChecking(buildLink, Handlers.mergeAlgorithms[i])
println 'Requested Travis build'
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package services.S3MHandlersAnalysis.datacollection

import main.project.MergeCommit
import main.project.Project
import main.util.ProcessRunner
import services.S3MHandlersAnalysis.util.Utils

import java.nio.file.Files
import java.nio.file.Path
import java.util.stream.Collectors

class MergeScenarioCollector {

static List<Path> collectMergeScenarios(Project project, MergeCommit mergeCommit) {
return getModifiedJavaFiles(project, mergeCommit).stream()
.map(modifiedFile -> storeAndRetrieveMergeQuadruple(project, mergeCommit, modifiedFile))
.map(quadruple -> quadruple.getV4().getParent())
.collect(Collectors.toList())
}

private static Tuple4<Path, Path, Path, Path> storeAndRetrieveMergeQuadruple(Project project, MergeCommit mergeCommit, String modifiedFile) {
Path leftFile = storeFile(project, mergeCommit, modifiedFile, mergeCommit.getLeftSHA(), 'left')
Path baseFile = storeFile(project, mergeCommit, modifiedFile, mergeCommit.getAncestorSHA(), 'base')
Path rightFile = storeFile(project, mergeCommit, modifiedFile, mergeCommit.getRightSHA(), 'right')
Path mergeFile = storeFile(project, mergeCommit, modifiedFile, mergeCommit.getSHA(), 'merge')
return new Tuple4(leftFile, baseFile, rightFile, mergeFile)
}

private static Path storeFile(Project project, MergeCommit mergeCommit, String modifiedFile, String commitSHA, String fileName) {
Path mergeScenarioDirectory = Utils.commitFilesPath(project, mergeCommit).resolve(modifiedFile)
createDirectories(mergeScenarioDirectory)

Path filePath = mergeScenarioDirectory.resolve("${fileName}.java")
Files.deleteIfExists(filePath)
filePath.toFile() << getFileContent(project, modifiedFile, commitSHA)
return filePath
}

private static String getFileContent(Project project, String modifiedFile, String commitSHA) {
StringBuilder fileContent = new StringBuilder()

Process gitShow = ProcessRunner.runProcess(project.getPath(), "git", "show", "${commitSHA}:${modifiedFile}")
gitShow.getInputStream().eachLine {
fileContent.append(it).append('\n')
}
return fileContent.toString()
}

private static List<String> getModifiedJavaFiles(Project project, MergeCommit mergeCommit) {
Process gitDiffTree = ProcessRunner.runProcess(project.getPath(), "git", "diff-tree", "--no-commit-id", "--name-status", "-r", mergeCommit.getSHA(), mergeCommit.getAncestorSHA())
List<String> modifiedFiles = gitDiffTree.getInputStream().readLines()

return modifiedFiles.stream()
.filter(MergeScenarioCollector::isModifiedFile)
.filter(MergeScenarioCollector::isJavaFile)
.map(MergeScenarioCollector::getPath)
.collect(Collectors.toList())
}

private static boolean isModifiedFile(String line) {
return line.charAt(0) == 'M' as char
}

private static boolean isJavaFile(String line) {
return line.endsWith('.java')
}

private static String getPath(String line) {
return line.substring(1).trim()
}

private static void createDirectories(Path path) {
path.toFile().mkdirs()
}

}
78 changes: 78 additions & 0 deletions src/services/S3MHandlersAnalysis/datacollection/S3MRunner.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package services.S3MHandlersAnalysis.datacollection

import main.util.ProcessRunner
import services.S3MHandlersAnalysis.Handlers

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption

class S3MRunner {

static final Path S3M_PATH = Paths.get("src/services/S3MHandlersAnalysis/s3m.jar")

static void collectS3MResults(List<Path> mergeScenarios, List<Handlers> handlers) {
mergeScenarios.parallelStream()
.forEach(mergeScenario -> runHandlerVariants(mergeScenario, handlers))
}

private static void runHandlerVariants(Path mergeScenario, List<Handlers> handlers) {
Path leftFile = getInvolvedFile(mergeScenario, 'left')
Path baseFile = getInvolvedFile(mergeScenario, 'base')
Path rightFile = getInvolvedFile(mergeScenario, 'right')

// To extend the analysis for other handlers, clone and modify the following conditional.
if (handlers.contains(Handlers.Renaming)) {
runS3M(leftFile, baseFile, rightFile, 'CT.java', Handlers.Renaming, '-hmcrdov')
runS3M(leftFile, baseFile, rightFile, 'SF.java', Handlers.Renaming, '-r', 'SAFE')
runS3M(leftFile, baseFile, rightFile, 'MM.java', Handlers.Renaming, '-r', 'MERGE')
runS3M(leftFile, baseFile, rightFile, 'KB.java', Handlers.Renaming, '-r', 'BOTH')
}
}

private static void runS3M(Path leftFile, Path baseFile, Path rightFile, String outputFileName, Handlers handler, String... additionalParameters) {
Process S3M = ProcessRunner.startProcess(buildS3MProcess(leftFile, baseFile, rightFile, outputFileName, handler, additionalParameters))
S3M.getInputStream().eachLine {
//println it
}
S3M.waitFor()

renameUnstructuredMergeFile(baseFile.getParent(), handler.name(), outputFileName)
}

private static void renameUnstructuredMergeFile(Path mergeScenario, String handlerName, String outputFileName) {
Path currentUnstructuredMergeFile = mergeScenario.resolve(handlerName).resolve("${outputFileName}.merge")
Path renamedUnstructuredMergeFile = mergeScenario.resolve("textual.java")
Files.move(currentUnstructuredMergeFile, renamedUnstructuredMergeFile, StandardCopyOption.REPLACE_EXISTING)
}

private static ProcessBuilder buildS3MProcess(Path leftFile, Path baseFile, Path rightFile, String outputFileName, Handlers handler, String... additionalParameters) {
ProcessBuilder S3M = ProcessRunner.buildProcess(getParentAsString(S3M_PATH))
List<String> parameters = buildS3MParameters(leftFile, baseFile, rightFile, outputFileName, handler.name(), additionalParameters)
S3M.command().addAll(parameters)
return S3M
}

private static List<String> buildS3MParameters(Path leftFile, Path baseFile, Path rightFile, String outputFileName, String handlerName, String... additionalParameters) {
List<String> parameters = ['java', '-jar', getNameAsString(S3M_PATH), leftFile.toString(), baseFile.toString(), rightFile.toString(), '-o', getOutputPath(baseFile.getParent(), handlerName, outputFileName).toString(), '-c', 'false', '-l', 'false']
parameters.addAll(additionalParameters.toList())
return parameters
}

private static Path getOutputPath(Path mergeScenario, String handlerName, String fileName) {
return mergeScenario.resolve(handlerName).resolve(fileName)
}

private static String getParentAsString(Path path) {
return path.getParent().toString()
}

private static String getNameAsString(Path path) {
return path.getFileName().toString()
}

private static Path getInvolvedFile(Path mergeScenario, String fileName) {
return mergeScenario.resolve("${fileName}.java").toAbsolutePath()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package services.S3MHandlersAnalysis.datacollection

import main.project.MergeCommit
import main.project.Project
import services.S3MHandlersAnalysis.implementations.OutputProcessor
import services.S3MHandlersAnalysis.util.MergeCommitSummary
import services.S3MHandlersAnalysis.util.MergeScenarioSummary
import services.S3MHandlersAnalysis.util.Utils

import java.nio.file.Path

class SpreadsheetBuilder {
private static final String GLOBAL_SPREADSHEET_HEADER = 'project,merge commit,number of modified files,number of TM conflicts,number of CT conflicts,number of SF conflicts,number of MM conflicts,number of KB conflicts,handlers have the same outputs,handlers have the same conflicts,notes,false positives,false negatives,travis builds,,,'
private static final String COMMIT_SPREADSHEET_HEADER = 'project,merge commit,file,number of TM conflicts,number of CT conflicts,number of SF conflicts,number of MM conflicts,number of KB conflicts,CT text = SF text,CT text = MM text,CT text = KB text,SF text = MM text, SF text = KB text,MM text = KB text,CT conflicts = SF conflicts,CT conflicts = MM conflicts,CT conflicts = KB conflicts,SF conflicts = MM conflicts,SF conflicts = KB conflicts,MM conflicts = KB conflicts'
private static final String SPREADSHEET_NAME = 'results.csv'

static synchronized void buildSpreadsheets(Project project, MergeCommit mergeCommit, MergeCommitSummary summary) {
buildGlobalSpreadsheet(project, mergeCommit, summary)
buildCommitSpreadsheet(project, mergeCommit, summary.mergeScenarioSummaries)
}

private static void buildCommitSpreadsheet(Project project, MergeCommit mergeCommit, List<MergeScenarioSummary> summaries) {
Path spreadsheetPath = Utils.commitFilesPath(project, mergeCommit).resolve(SPREADSHEET_NAME)
File spreadsheet = spreadsheetPath.toFile()
appendHeader(spreadsheet, COMMIT_SPREADSHEET_HEADER)

summaries.each { summary ->
appendLineToSpreadsheet(spreadsheet, appendAfterProjectAndMergeCommitLinks(project, mergeCommit, summary.toString()))
}
}

private static void buildGlobalSpreadsheet(Project project, MergeCommit mergeCommit, MergeCommitSummary summary) {
Path spreadsheetPath = Utils.getOutputPath().resolve(SPREADSHEET_NAME)
File spreadsheet = spreadsheetPath.toFile()
appendHeader(spreadsheet, GLOBAL_SPREADSHEET_HEADER)

appendLineToSpreadsheet(spreadsheet, appendAfterProjectAndMergeCommitLinks(project, mergeCommit, summary.toString()))
}

private static void appendHeader(File spreadsheet, String header) {
if (!spreadsheet.exists()) {
appendLineToSpreadsheet(spreadsheet, header)
}
}

private static String appendAfterProjectAndMergeCommitLinks(Project project, MergeCommit mergeCommit, String string) {
String projectName = Utils.getHyperLink(OutputProcessor.ANALYSIS_REMOTE_URL + "/${project.getName()}", project.getName())
String commitSHA = Utils.getHyperLink(OutputProcessor.ANALYSIS_REMOTE_URL + "/${project.getName()}/${mergeCommit.getSHA()}", mergeCommit.getSHA())
return "${projectName},${commitSHA},${string}"
}

private static void appendLineToSpreadsheet(File spreadsheet, String line) {
spreadsheet << "${line.replaceAll('\\\\', '/')}\n"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package services.S3MHandlersAnalysis.implementations

import main.project.MergeCommit
import main.project.Project
import main.util.ProcessRunner
import services.S3MHandlersAnalysis.datacollection.MergeScenarioCollector

class CommitFilter implements main.interfaces.CommitFilter {

@Override
boolean applyFilter(Project project, MergeCommit mergeCommit) {
return thereIsAtLeastOneMergeScenario(project, mergeCommit)
}

private static boolean thereIsAtLeastOneMergeScenario(Project project, MergeCommit mergeCommit) {
Process gitDiffTree = ProcessRunner.runProcess(project.getPath(), "git", "diff-tree", "--no-commit-id", "--name-status", "-r", mergeCommit.getSHA(), mergeCommit.getAncestorSHA())
List<String> modifiedFiles = gitDiffTree.getInputStream().readLines()

return modifiedFiles.stream()
.filter(MergeScenarioCollector::isModifiedFile)
.filter(MergeScenarioCollector::isJavaFile)
.count() > 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package services.S3MHandlersAnalysis.implementations

import main.interfaces.DataCollector
import main.project.MergeCommit
import main.project.Project
import services.S3MHandlersAnalysis.datacollection.DataAnalyser
import services.S3MHandlersAnalysis.datacollection.MergeScenarioCollector
import services.S3MHandlersAnalysis.datacollection.SpreadsheetBuilder
import services.S3MHandlersAnalysis.util.MergeCommitSummary

import java.nio.file.Path

class MergesCollector implements DataCollector {
// groovy -cp src src/main/app/MiningFramework.groovy -a b22d2fc334ece38945974c789654e8f56d812b02 -i services.S3MHandlersAnalysis.MiningModule projects.csv

@Override
void collectData(Project project, MergeCommit mergeCommit) {
List<Path> mergeScenarios = MergeScenarioCollector.collectMergeScenarios(project, mergeCommit)
println 'Collected merge scenarios'

// S3MRunner.collectS3MResults(mergeScenarios, [Handlers.Renaming])
// println 'Collected S3M results'

MergeCommitSummary summary = DataAnalyser.analyseScenarios(project, mergeCommit, mergeScenarios)
println 'Summarized collected data'

SpreadsheetBuilder.buildSpreadsheets(project, mergeCommit, summary)
println 'Built spreadsheets'
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package services.S3MHandlersAnalysis.implementations

import services.S3MHandlersAnalysis.util.Utils

import java.nio.file.Path
import java.nio.file.Paths

class OutputProcessor implements main.interfaces.OutputProcessor {

private static final Path ANALYSIS_REPOSITORY_PATH = Paths.get('../merge-tools')
static final String ANALYSIS_REMOTE_URL = "https://github.com/jvcoutinho/merge-tools/tree/master/s3m-handlers-analysis"

@Override
void processOutput() {
stageAndPushData()
println 'Pushed data to remote analysis repository'
}

private static void stageAndPushData() {
// Stage changes.
Utils.runGitCommand(ANALYSIS_REPOSITORY_PATH, 'add', '.')

// Commit changes.
Utils.runGitCommand(ANALYSIS_REPOSITORY_PATH, 'commit', '-m', 'Collected data')

// Push changes.
Utils.runGitCommand(ANALYSIS_REPOSITORY_PATH, 'push', '--force-with-lease')
}


}
Loading

0 comments on commit 6900788

Please sign in to comment.