-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Implementation of S3M's Handlers Analysis
- Loading branch information
Showing
16 changed files
with
871 additions
and
1 deletion.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/services/dataCollectors/S3MMergesCollector/DataAnalyser.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} | ||
} | ||
} | ||
|
||
} |
85 changes: 85 additions & 0 deletions
85
src/main/services/dataCollectors/S3MMergesCollector/MergeScenarioCollector.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
src/main/services/dataCollectors/S3MMergesCollector/MergesCollector.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} | ||
|
||
} |
85 changes: 85 additions & 0 deletions
85
src/main/services/dataCollectors/S3MMergesCollector/S3MRunner.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/main/services/dataCollectors/S3MMergesCollector/SpreadsheetBuilder.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.