package harman;
import fr.inria.diversify.buildSystem.AbstractBuilder;
import fr.inria.diversify.diversification.InputProgram;
import fr.inria.diversify.util.JavaOutputProcessorWithFilter;
import fr.inria.diversify.util.Log;
import org.apache.commons.io.FileUtils;
import spoon.compiler.Environment;
import spoon.processing.AbstractProcessor;
import spoon.processing.ProcessingManager;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.QueueProcessingManager;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Created by Simon on 19/02/15.
*/
public class Algo {
protected Result result;
int cloneCount = 0;
protected int searchRadius = 10;
int interactionLevel = 1;
protected AbstractBuilder builder;
protected File jacocoDir;
protected List<CtClass> applicationClasses;
protected Map<String, List<Double>> originalTestCoverages;
protected Map<String, List<Double>> neighboursCoverage;
protected InputProgram inputProgram;
protected String tmpDir;
private Map<CtMethod, List<CtLiteral>> inputVectors;
public Algo(String tmpDir, InputProgram inputProgram, AbstractBuilder builder, List<CtClass> applicationClasses, Result result) {
this.result = result;
this.tmpDir = tmpDir;
this.inputProgram = inputProgram;
this.builder = builder;
this.jacocoDir = new File(tmpDir+"/target/site/junco");
this.applicationClasses = applicationClasses;
}
public void setInteractionLevel(int interactionLevel) {
this.interactionLevel = interactionLevel;
}
public void setSearchRadius(int searchRadius) {
this.searchRadius = searchRadius;
}
protected void initFitnessValue() throws IOException, InterruptedException {
originalTestCoverages = new HashMap<>();
initFitnessValue(originalTestCoverages);
}
protected void initFitnessValue(Map<String, List<Double>> coverages) throws IOException, InterruptedException {
builder.runBuilder();
if(jacocoDir.exists()) {
for (File file : jacocoDir.listFiles()) {
if (file.getName().endsWith(".exec")) {
String testName = file.getName().substring(0, file.getName().length() - 5);
String dir = tmpDir + "/target/classes/";
CoverageInfo coverageInfo = new CoverageInfo(dir, file);
coverageInfo.create();
coverages.put(testName, coverageInfo.branchCoverageInfo(applicationClasses));
}
}
}
}
public void testDataRegeneration(Set<CtMethod> testSuite, CtClass testClass) {
Random r = new Random();
int i = 0;
setBuilderGoal(testClass);
for (CtMethod test : testSuite) {
i++;
if(!getNumber(test).isEmpty()) {
double fitnessCurrentSol = 0;
CtMethod currentSol = test;
int count = 0;
while (count < searchRadius) {
Log.info("original method {} ({}/{}), current searchRadius: {}",test.getSimpleName(), i, testSuite.size(), count);
CtMethod nextSol = null;
try {
List<CtMethod> neighbours = generateNeighbours(currentSol, testClass);
while (!neighbours.isEmpty()) {
CtMethod n = neighbours.remove(r.nextInt(neighbours.size()));
if (fitness(test, n) > fitnessCurrentSol) {
nextSol = n;
break;
}
}
} catch (Exception e) {
e.printStackTrace();
Log.info("error during neighbours evaluation");
}
if (nextSol == null) {
break;
} else {
fitnessCurrentSol = fitness(test, nextSol);
currentSol = nextSol;
count++;
}
}
if (currentSol != test) {
Log.info("new method add: {}",currentSol.getSimpleName());
Log.info("original: {}, new {}", getNumber(test), getNumber(currentSol));
result.addNewTest(testClass, test, currentSol, count, cloneCount);
cloneCount = 0;
}
}
}
}
protected void setBuilderGoal(CtClass testClass) {
builder.setGoals(new String[]{"-Dtest="+testClass.getSimpleName(),"test"});
}
protected double fitness(CtMethod original, CtMethod currentSol) {
List<CtLiteral> currentSolVector = inputVectors.get(currentSol);
List<CtLiteral> originalVector = getNumber(original);
double vectorDistance = euclidienDistance(currentSolVector, originalVector);
double coverageDistance = coverageDistance(currentSol);
if(coverageDistance == 0 && vectorDistance > 0) {
return vectorDistance;
}
if(coverageDistance(currentSol) > 0 && vectorDistance > 0) {
return -coverageDistance;
}
return 0;
}
protected double euclidienDistance(List<CtLiteral> currentSolVector, List<CtLiteral> originalVector) {
double vectorDistance = 0;
for(int i = 0; i < originalVector.size(); i++ ) {
double tmp = ((Number) currentSolVector.get(i).getValue()).doubleValue() - ((Number) originalVector.get(i).getValue()).doubleValue();
vectorDistance += tmp * tmp;
}
return Math.sqrt(vectorDistance);
}
protected double coverageDistance(CtMethod method) {
CtClass cl = method.getParent(CtClass.class);
String originalTestName = cl.getQualifiedName() + "." +method.getSimpleName().split("____")[0];
List<Double> originalTestCoverage = originalTestCoverages.get(originalTestName);
List<Double> neighbourCoverage = neighboursCoverage.get(cl.getQualifiedName() + "." +method.getSimpleName());
if(originalTestCoverage == null
|| neighbourCoverage == null
|| originalTestCoverage.size() != neighbourCoverage.size()) {
return 100;
} else {
double distance = 0;
for (int i = 0; i < originalTestCoverage.size(); i++) {
double tmp = originalTestCoverage.get(i) - neighbourCoverage.get(i);
distance += tmp * tmp;
}
return Math.sqrt(distance);
}
}
protected List<CtMethod> generateNeighbours(CtMethod test, CtClass testClass) throws InterruptedException, IOException {
List<CtMethod> neighbours = new ArrayList<>();
Collection<List<CtLiteral>> combi = new ArrayList<>();
List<CtLiteral> number = getNumber(test);
combi.addAll(combi(number, interactionLevel, (Object n) -> literalPlusOne(n)));
combi.addAll(combi(number, interactionLevel, (Object n) -> literalMinusOne(n)));
combi.addAll(combi(number, interactionLevel, (Object n) -> literalMultiTwo(n)));
combi.addAll(combi(number, interactionLevel, (Object n) -> literalDivTwo(n)));
combi = filterCombi(combi);
inputVectors = new HashMap<>();
for(List<CtLiteral> c : combi) {
neighbours.add(generateNeighbour(test, c));
}
generateNewSource(testClass, neighbours);
computeNeighboursFitness(neighbours, builder.getFailedTests());
restoreTestclass(testClass, neighbours);
FileUtils.forceDelete(jacocoDir);
return neighbours;
}
protected void restoreTestclass(CtClass testClass, List<CtMethod> neighbours) {
for(CtMethod neighbour : neighbours) {
testClass.removeMethod(neighbour);
}
}
protected void computeNeighboursFitness(List<CtMethod> neighbours, List<String> failedTests) throws IOException, InterruptedException {
neighboursCoverage = new HashMap<>();
initFitnessValue(neighboursCoverage);
}
protected void generateNewSource(CtClass testClass, List<CtMethod> testMethods) {
for(CtMethod method : testMethods) {
testClass.addMethod(method);
}
writeJavaClass();
}
protected void writeJavaClass() {
File fileFrom = new File(inputProgram.getAbsoluteTestSourceCodeDir());
File out = new File(tmpDir + "/" + inputProgram.getRelativeTestSourceCodeDir());
Environment env = inputProgram.getFactory().getEnvironment();
AbstractProcessor processor = new JavaOutputProcessorWithFilter(out, new DefaultJavaPrettyPrinter(env), allClassesName(fileFrom));
applyProcessor(inputProgram.getFactory(), processor);
}
protected void applyProcessor(Factory factory, AbstractProcessor processor) {
ProcessingManager pm = new QueueProcessingManager(factory);
pm.addProcessor(processor);
pm.process();
}
protected List<String> allClassesName(File dir) {
List<String> list = new ArrayList<>();
for(File file : dir.listFiles())
if(file.isDirectory())
list.addAll(allClassesName(file));
else {
String name = file.getName();
if(name.endsWith(".java")) {
String[] tmp = name.substring(0, name.length() - 5).split("/");
list.add(tmp[tmp.length - 1]);
}
}
return list;
}
protected CtMethod generateNeighbour(CtMethod test, List<CtLiteral> numbers) {
CtMethod cloned_method = cloneMethod(test);
List<CtLiteral> cloneNumbers = getNumber(cloned_method);
for (int index = 0; index < numbers.size(); index++) {
cloneNumbers.get(index).replace(numbers.get(index));
}
inputVectors.put(cloned_method, numbers);
return cloned_method;
}
protected CtMethod cloneMethod(CtMethod method) {
CtMethod cloned_method = inputProgram.getFactory().Core().clone(method);
cloned_method.setParent(method.getParent());
//rename the clone
String originalTestName = method.getSimpleName().split("____")[0];
cloned_method.setSimpleName(originalTestName + "____"+cloneCount);
cloneCount++;
CtAnnotation toRemove = cloned_method.getAnnotations().stream()
.filter(annotation -> annotation.toString().contains("Override"))
.findFirst().orElse(null);
if(toRemove != null) {
cloned_method.removeAnnotation(toRemove);
}
return cloned_method;
}
protected List<CtLiteral> getNumber(CtMethod method) {
return Query.getElements(method, new TypeFilter<CtLiteral>(CtLiteral.class)).stream()
.filter(literal -> literal.getValue() instanceof Number )
.collect(Collectors.toList());
}
private Set<List<CtLiteral>> combiList;
protected Set<List<CtLiteral>> combi(List<CtLiteral> elem, int interactionLevel, Function<Object, Number> f) {
combiList = new HashSet<>();
combi(elem, new HashSet<>(), interactionLevel, f);
return combiList;
}
protected void combi(List<CtLiteral> elem, Set<Integer> alreadyModif, int interactionLevel, Function<Object, Number> f) {
if(interactionLevel != 0) {
for (int index = 0; index < elem.size(); index++) {
if (!alreadyModif.contains(index)) {
HashSet copyAlreadyModif = new HashSet(alreadyModif);
copyAlreadyModif.add(index);
List<CtLiteral> cloneElem = cloneLiteralList(elem);
if(interactionLevel == 1) {
combiList.add(cloneElem);
}
modif(cloneElem, index, f);
combi(cloneElem, copyAlreadyModif, interactionLevel - 1, f);
}
}
}
}
private void modif(List<CtLiteral> cloneElem, int index, Function<Object, Number> f) {
CtLiteral literal = cloneElem.get(index);
Object value = literal.getValue();
Number newValue = f.apply(value);
if(value instanceof Integer) {
literal.setValue(newValue.intValue());
} else if(value instanceof Long) {
literal.setValue(newValue.longValue());
} else if(value instanceof Double) {
literal.setValue(newValue.doubleValue());
} else if(value instanceof Short) {
literal.setValue(newValue.shortValue());
} else if(value instanceof Float) {
literal.setValue(newValue.floatValue());
} else if(value instanceof Byte) {
literal.setValue(newValue.byteValue());
}
}
protected Number literalPlusOne(Object literal) {
Double value = ((Number) literal).doubleValue();
return value + 1;
}
protected Number literalMinusOne(Object literal) {
Double value = ((Number) literal).doubleValue();
return value - 1;
}
protected Number literalMultiTwo(Object literal) {
Double value = ((Number) literal).doubleValue();
return value * 2;
}
protected Number literalDivTwo(Object literal) {
Double value = ((Number) literal).doubleValue();
return value / 2;
}
protected List<CtLiteral> cloneLiteralList(List<CtLiteral> list) {
List<CtLiteral> clone = new ArrayList<>(list.size());
for(CtLiteral literal : list) {
clone.add(inputProgram.getFactory().Core().clone(literal));
}
return clone;
}
protected Set<List<CtLiteral>> filterCombi(Collection<List<CtLiteral>> combi) {
Map<List<CtLiteral>, List<Number>> map = new HashMap<>();
for( List<CtLiteral> lit: combi) {
List<Number> numbers = lit.stream()
.map(l -> (Number) l.getValue())
.collect(Collectors.toList());
if(!map.values().contains(numbers)) {
map.put(lit,numbers);
}
}
return map.keySet();
}
}