-
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.
Merge pull request #197 from jpedroh/feat-files-comparison
feat: Add more Data Collectors for Generic Merge study
- Loading branch information
Showing
10 changed files
with
284 additions
and
3 deletions.
There are no files selected for viewing
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
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
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
90 changes: 90 additions & 0 deletions
90
src/main/services/dataCollectors/GenericMerge/MergeConflictsComparator.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,90 @@ | ||
package services.dataCollectors.GenericMerge | ||
|
||
|
||
import interfaces.DataCollector | ||
import org.apache.logging.log4j.LogManager | ||
import org.apache.logging.log4j.Logger | ||
import project.MergeCommit | ||
import project.Project | ||
import services.dataCollectors.S3MMergesCollector.MergeScenarioCollector | ||
import services.mergeScenariosFilters.NonFastForwardMergeScenarioFilter | ||
import services.util.MergeConflict | ||
import util.CsvUtils | ||
|
||
import java.nio.file.Files | ||
import java.nio.file.Path | ||
import java.util.stream.Stream | ||
|
||
class MergeConflictsComparator implements DataCollector { | ||
private static Logger LOG = LogManager.getLogger(MergeConflictsComparator.class) | ||
|
||
private static final String GENERIC_MERGE_FILE_NAME = "merge.generic_merge.java" | ||
private static final String JDIME_FILE_NAME = "merge.jdime.java" | ||
|
||
@Override | ||
void collectData(Project project, MergeCommit mergeCommit) { | ||
LOG.trace("Starting execution of Merge Conflicts Comparator on project ${project.getName()} and merge commit ${mergeCommit.getSHA()}") | ||
|
||
def conflictsComparisons = MergeScenarioCollector.collectMergeScenarios(project, mergeCommit) | ||
.parallelStream() | ||
.filter(NonFastForwardMergeScenarioFilter::isNonFastForwardMergeScenario) | ||
.filter(MergeConflictsComparator::hasResponseFromBothTools) | ||
.filter(MergeConflictsComparator::hasConflictsInBothTools) | ||
.map(MergeConflictsComparator::extractConflictsFromFiles) | ||
.flatMap(MergeConflictsComparator::compareMergeConflicts(project, mergeCommit)) | ||
.map(CsvUtils::toCsvRepresentation) | ||
|
||
def reportFile = new File(GenericMergeConfig.GENERIC_MERGE_REPORT_MERGE_CONFLICTS) | ||
def fileContent = conflictsComparisons.collect(CsvUtils.asLines()) | ||
if (fileContent.isBlank() || fileContent.isEmpty()) { | ||
LOG.trace("Finished execution of Merge Conflicts Comparator on project ${project.getName()} and merge commit ${mergeCommit.getSHA()} without conflicts") | ||
return | ||
} | ||
reportFile << fileContent << "\n" | ||
|
||
LOG.trace("Finished execution of Merge Conflicts Comparator on project ${project.getName()} and merge commit ${mergeCommit.getSHA()}") | ||
} | ||
|
||
private static boolean hasResponseFromBothTools(Path scenario) { | ||
LOG.trace("Checking if has response from both tools for ${scenario.toString()}") | ||
return Files.exists(scenario.resolve(GENERIC_MERGE_FILE_NAME)) && Files.exists(scenario.resolve(JDIME_FILE_NAME)) | ||
} | ||
|
||
private static boolean hasConflictsInBothTools(Path scenario) { | ||
LOG.trace("Checking if both files have conflicts") | ||
return MergeConflict.getConflictsNumber(scenario.resolve(GENERIC_MERGE_FILE_NAME)) > 0 && MergeConflict.getConflictsNumber(scenario.resolve(JDIME_FILE_NAME)) > 0 | ||
} | ||
|
||
private static Tuple3<Path, Set<MergeConflict>, Set<MergeConflict>> extractConflictsFromFiles(Path scenario) { | ||
LOG.trace("Extracting conflicts from files in ${scenario.toString()}") | ||
return new Tuple3(scenario, MergeConflict.extractMergeConflicts(scenario.resolve(GENERIC_MERGE_FILE_NAME)), | ||
MergeConflict.extractMergeConflicts(scenario.resolve(JDIME_FILE_NAME))) | ||
} | ||
|
||
private static Closure<Stream<List<String>>> compareMergeConflicts(Project project, MergeCommit mergeCommit) { | ||
return (Tuple3<Path, Set<MergeConflict>, Set<MergeConflict>> mergeConflicts) -> { | ||
def scenario = mergeConflicts.getV1() | ||
def genericMergeConflicts = mergeConflicts.getV2() | ||
def jDimeConflicts = mergeConflicts.getV3() | ||
|
||
return genericMergeConflicts.withIndex().parallelStream().flatMap(genericMergeTuple -> { | ||
def genericMergeConflict = genericMergeTuple.getV1() | ||
def i = genericMergeTuple.getV2() | ||
|
||
return jDimeConflicts.withIndex().parallelStream().map(jDimeTuple -> { | ||
def jDimeConflict = jDimeTuple.getV1() | ||
def j = jDimeTuple.getV2() | ||
|
||
LOG.trace("Checking if conflicts generic_merge_conflict_${i} and jdime_conflict_${j} are equal") | ||
|
||
return [project.getName(), | ||
mergeCommit.getSHA(), | ||
scenario.toString(), | ||
"generic_merge_conflict_${i}", | ||
"jdime_conflict_${j}", | ||
genericMergeConflict.equals(jDimeConflict).toString()] | ||
}) | ||
}) | ||
} | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/services/dataCollectors/GenericMerge/MergeToolsComparator.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.GenericMerge | ||
|
||
import interfaces.DataCollector | ||
import org.apache.logging.log4j.LogManager | ||
import org.apache.logging.log4j.Logger | ||
import project.MergeCommit | ||
import project.Project | ||
import services.dataCollectors.S3MMergesCollector.MergeScenarioCollector | ||
import services.mergeScenariosFilters.NonFastForwardMergeScenarioFilter | ||
import util.CsvUtils | ||
|
||
import java.nio.file.Path | ||
|
||
class MergeToolsComparator implements DataCollector { | ||
private static Logger LOG = LogManager.getLogger(MergeToolsComparator.class) | ||
|
||
@Override | ||
void collectData(Project project, MergeCommit mergeCommit) { | ||
LOG.trace("Starting execution of Merge Tools Comparator on project ${project.getName()} and merge commit ${mergeCommit.getSHA()}") | ||
|
||
def results = MergeScenarioCollector.collectMergeScenarios(project, mergeCommit) | ||
.parallelStream() | ||
.filter(NonFastForwardMergeScenarioFilter::isNonFastForwardMergeScenario) | ||
.map(scenario -> checkIfOutputsAreEquivalent(project, mergeCommit, scenario)) | ||
.map(CsvUtils::toCsvRepresentation) | ||
|
||
def reportFile = new File(GenericMergeConfig.GENERIC_MERGE_REPORT_FILES_EQUIVALENT) | ||
reportFile << results.collect(CsvUtils.asLines()) << "\n" | ||
|
||
LOG.trace("Finished execution of Merge Tools Comparator on project ${project.getName()} and merge commit ${mergeCommit.getSHA()}") | ||
} | ||
|
||
private static List<String> checkIfOutputsAreEquivalent(Project project, MergeCommit mergeCommit, Path scenario) { | ||
LOG.trace("Starting to check if output for ${scenario.toString()} are equivalents") | ||
|
||
def genericMergePath = scenario.resolve("merge.generic_merge.java") | ||
def jDimePath = scenario.resolve("merge.jdime.java") | ||
|
||
def result = [project.getName(), | ||
mergeCommit.getSHA(), | ||
scenario.toString(), | ||
FileSyntacticDiff.areFilesSyntacticallyEquivalent(genericMergePath, jDimePath).toString()] | ||
|
||
LOG.trace("Finished checking if output for ${scenario.toString()} are equivalents") | ||
return result | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/main/services/dataCollectors/GenericMerge/ScenarioLOCsCounter.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,56 @@ | ||
package services.dataCollectors.GenericMerge | ||
|
||
import interfaces.DataCollector | ||
import org.apache.logging.log4j.LogManager | ||
import org.apache.logging.log4j.Logger | ||
import org.json.JSONObject | ||
import project.MergeCommit | ||
import project.Project | ||
import services.dataCollectors.S3MMergesCollector.MergeScenarioCollector | ||
import services.mergeScenariosFilters.NonFastForwardMergeScenarioFilter | ||
import util.CsvUtils | ||
import util.ProcessRunner | ||
|
||
import java.nio.file.Path | ||
|
||
/** | ||
* Counts the number of LOCs (ignoring comments and blank lines) in base, left and right. | ||
* It assumes that there's an executable for cloc in ./dependencies (symbolic links are allowed).*/ | ||
class ScenarioLOCsCounter implements DataCollector { | ||
private static Logger LOG = LogManager.getLogger(MergeToolsComparator.class) | ||
|
||
@Override | ||
void collectData(Project project, MergeCommit mergeCommit) { | ||
def scenarios = MergeScenarioCollector.collectMergeScenarios(project, mergeCommit) | ||
.parallelStream() | ||
.filter(NonFastForwardMergeScenarioFilter::isNonFastForwardMergeScenario) | ||
.map((scenario) -> { | ||
LOG.trace("Starting to count LOCs in ${scenario.toString()}") | ||
def base = countLinesOfCodeInFile(scenario.resolve("base.java")) | ||
def left = countLinesOfCodeInFile(scenario.resolve("left.java")) | ||
def right = countLinesOfCodeInFile(scenario.resolve("right.java")) | ||
def total = base + left + right | ||
|
||
return [project.getName(), mergeCommit.getSHA(), scenario.toString(), base.toString(), left.toString(), right.toString(), total.toString()] | ||
}) | ||
.map(CsvUtils::toCsvRepresentation) | ||
|
||
def reportFile = new File(GenericMergeConfig.GENERIC_MERGE_REPORT_SCENARIO_LOCS_FILE_NAME) | ||
reportFile << scenarios.collect(CsvUtils.asLines()) << "\n" | ||
} | ||
|
||
private static int countLinesOfCodeInFile(Path file) { | ||
def clocProcessBuilder = ProcessRunner.buildProcess("./dependencies", | ||
"./cloc", | ||
file.toAbsolutePath().toString(), | ||
"--json") | ||
|
||
def output = ProcessRunner.startProcess(clocProcessBuilder) | ||
output.waitFor() | ||
|
||
def jsonOutput = new JSONObject(output.getInputStream().readLines().join('\n')) | ||
int sumCode = jsonOutput.getJSONObject("SUM").getInt("code") | ||
|
||
return sumCode | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
src/main/services/dataCollectors/GenericMerge/UnstructuredMergeCollector.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,52 @@ | ||
package services.dataCollectors.GenericMerge | ||
|
||
import interfaces.DataCollector | ||
import project.MergeCommit | ||
import project.Project | ||
import services.dataCollectors.S3MMergesCollector.MergeScenarioCollector | ||
import services.mergeScenariosFilters.NonFastForwardMergeScenarioFilter | ||
import util.CsvUtils | ||
import util.ProcessRunner | ||
|
||
import java.nio.file.Path | ||
|
||
class UnstructuredMergeCollector implements DataCollector { | ||
@Override | ||
void collectData(Project project, MergeCommit mergeCommit) { | ||
def scenarios = MergeScenarioCollector.collectMergeScenarios(project, mergeCommit) | ||
.stream() | ||
.filter(NonFastForwardMergeScenarioFilter::isNonFastForwardMergeScenario) | ||
.map(scenario -> { | ||
def executionTime = runGitMergeFile(scenario) | ||
return [project.getName(), mergeCommit.getSHA(), scenario.toString(), executionTime] | ||
}) | ||
.map(CsvUtils::toCsvRepresentation) | ||
|
||
def reportFile = new File(GenericMergeConfig.GENERIC_MERGE_REPORT_UNSTRUCTURED_TIMES_FILE_NAME) | ||
reportFile << scenarios.collect(CsvUtils.asLines()) << "\n" | ||
} | ||
|
||
private static long runGitMergeFile(Path scenario) { | ||
def executionTimes = new ArrayList<Long>() | ||
|
||
for (int i = 0; i < GenericMergeConfig.NUMBER_OF_EXECUTIONS; i++) { | ||
long startTime = System.nanoTime() | ||
|
||
def processBuilder = ProcessRunner.buildProcess(GenericMergeConfig.BASE_EXPERIMENT_PATH, | ||
"git", | ||
"merge-file", | ||
scenario.resolve("left.java").toString(), | ||
scenario.resolve("base.java").toString(), | ||
scenario.resolve("right.java").toString()) | ||
ProcessRunner.startProcess(processBuilder).waitFor() | ||
|
||
long endTime = System.nanoTime() | ||
// If we're running more than one execution, we use the first one as a warm up | ||
if (GenericMergeConfig.NUMBER_OF_EXECUTIONS == 1 || i > 0) { | ||
executionTimes.add(endTime - startTime) | ||
} | ||
} | ||
|
||
return (long) (executionTimes.stream().reduce(0, (prev, cur) -> prev + cur) / executionTimes.size()) | ||
} | ||
} |
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
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
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,23 @@ | ||
package util | ||
|
||
import java.util.stream.Collectors | ||
|
||
class CsvUtils { | ||
/** | ||
* Converts a list into a CSV row. | ||
* @param List<String> items | ||
* @return The row separated by commas | ||
*/ | ||
static String toCsvRepresentation(List<String> items) { | ||
return items.join(',').replaceAll('\\\\', '/') | ||
} | ||
|
||
/** | ||
* Collects a Stream of Strings into a single String separated by line breaks | ||
* @param lines | ||
* @return | ||
*/ | ||
static asLines() { | ||
return Collectors.joining(System.lineSeparator()) | ||
} | ||
} |