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 #70

Merged
merged 4 commits into from
Jun 15, 2020
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
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,47 @@
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 {

/**
* 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.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 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()
}

}
86 changes: 86 additions & 0 deletions src/services/S3MHandlersAnalysis/datacollection/S3MRunner.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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")

/**
* 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
rafaelmotaalves marked this conversation as resolved.
Show resolved Hide resolved
}
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.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'

/**
* 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(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) {
rafaelmotaalves marked this conversation as resolved.
Show resolved Hide resolved
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,33 @@
package services.S3MHandlersAnalysis.implementations

import main.interfaces.DataCollector
import main.project.MergeCommit
import main.project.Project
import services.S3MHandlersAnalysis.Handlers
import services.S3MHandlersAnalysis.datacollection.DataAnalyser
import services.S3MHandlersAnalysis.datacollection.MergeScenarioCollector
import services.S3MHandlersAnalysis.datacollection.S3MRunner
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'
}

}
Loading