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

Implementation of S3M's Handlers Analysis #111

Merged
merged 8 commits into from
Jun 15, 2020
Merged
Binary file added dependencies/s3m.jar
Binary file not shown.
28 changes: 28 additions & 0 deletions src/main/injectors/S3MMiningModule.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package injectors

@Grab('com.google.inject:guice:4.2.2')
import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import interfaces.*
import services.commitFilters.S3MCommitFilter
import services.dataCollectors.S3MMergesCollector.MergesCollector
import services.outputProcessors.S3MOutputProcessor
import services.projectProcessors.ForkAndEnableTravisProcessor

class S3MMiningModule extends AbstractModule {

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

Multibinder<ProjectProcessor> projectProcessorBinder = Multibinder.newSetBinder(binder(), ProjectProcessor.class)
projectProcessorBinder.addBinding().to(ForkAndEnableTravisProcessor.class)

Multibinder<OutputProcessor> outputProcessorBinder = Multibinder.newSetBinder(binder(), OutputProcessor.class)
outputProcessorBinder.addBinding().to(S3MOutputProcessor.class)

bind(CommitFilter.class).to(S3MCommitFilter.class)
}

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

import interfaces.CommitFilter
import project.MergeCommit
import project.Project
import services.dataCollectors.S3MMergesCollector.MergeScenarioCollector
import util.ProcessRunner

class S3MCommitFilter implements 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,47 @@
package services.dataCollectors.S3MMergesCollector

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

import java.nio.file.Path

class DataAnalyser {

/**
* Analyses each merge scenario's directories after S3M has run. It constructs a {@link MergeScenarioSummary} for each
* merge scenario and a global {@link MergeCommitSummary} for each merge commit.
* @param project
* @param mergeCommit
* @param mergeScenarios
* @return a summary of results of the merge commit
*/
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,85 @@
package services.dataCollectors.S3MMergesCollector

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

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

/**
* Class responsible for collecting and storing eligible merge scenarios (modified from base java files).
*/
class MergeScenarioCollector {

/**
* Stores merge scenarios (left, base, right and merge files) encountered in the merge commit.
* @param project
* @param mergeCommit
* @return a list of directory paths where each merge scenario is located
*/
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()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package services.dataCollectors.S3MMergesCollector

import interfaces.DataCollector
import project.MergeCommit
import project.Project
import services.util.MergeCommitSummary
import util.Handlers

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,85 @@
package services.dataCollectors.S3MMergesCollector

import util.Handlers
import util.ProcessRunner
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("dependencies/s3m.jar")

/**
* Runs S3M for each merge scenario and for each handler. Store the results at the same directory
* the merge scenario is located, in a directory for each handler.
*
* To extend the analysis for more handlers, check {@link #runHandlerVariants(Path, List < Handlers >)}
* @param mergeScenarios
* @param handlers
*/
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,63 @@
package services.dataCollectors.S3MMergesCollector

import project.MergeCommit
import project.Project
import services.outputProcessors.S3MOutputProcessor
import services.util.MergeCommitSummary
import services.util.MergeScenarioSummary
import services.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'

/**
* Builds a global spreadsheet, based on the merge commit's summary, and a local spreadsheet, for each merge commit, based on
* the merge scenario's summary.
* @param project
* @param mergeCommit
* @param summary
*/
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(S3MOutputProcessor.ANALYSIS_REMOTE_URL + "/${project.getName()}", project.getName())
String commitSHA = Utils.getHyperLink(S3MOutputProcessor.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
Expand Up @@ -37,4 +37,4 @@ class FetchBuildsOutputProcessor implements OutputProcessor {
throw new ExternalScriptException(FETCH_JARS_PATH, exitStatus);
}
}
}
}
Loading